Infopath 2007, Sharepoint Workflows and Whatnot
Long time no blog! After 20 years in the legal industry I jumped ship and moved to the dark side. I'm officially consulting and figuring out how it fits into my daily life. So far, I'm really enjoying it and the work product itself isn't much different.... except that now I have to keep track of how long things take me to do and with time keeping comes much greater levels of frustration and immediacy when dealing with the joys of beta code. Most of my projects revolve in some way around Sharepoint and, in particular, what appears to have become my forte, Sharepoint workflows. I think this technology is set to lure companies into Sharepoint which then provides the platform for a lot of other Office infiltration.
With consulting and timekeeping, I haven't had a lot of time to blog but before Sharepoint/Office Connections (Vegas, baby... okay, okay, I have to admit, I've never been to Vegas so it will be interesting) I thought I'd do a brief update with some key pieces of code (because really you aren't here for anything else).
My love and hate affair with Infopath has been continuing. One of the driving forces behind Sharepoint workflows is the ability to design Infopath forms for use as task forms and content types. This is truly a lovely thing and saves developers massive amounts of time and energy (especially if you can off this UI work to a business analyst of some type) but there are some big huge problems with it. First and foremost, Infopath task forms do not have native support for repeating sections. What I mean by that is that you cannot simply pull and push repeating nodes out of the task form and into the task form. To date, I don't have a single workflow project where the functional requirements do not include task forms displaying the actual workflow data in each one. In other words, most workflows appear to involve some sort of New action in Sharepoint (usually with an Infopath content type), which launches the workflow and each workflow task form then displays all of or a portion of the original data (and I'm not talking about simple list properties that are promoted -- you can't promote and entire repeating table!).
The wonderful Mr. Sanderman was kind enough to shoot the pain with me while I was getting through this and eventually, I managed to get it all working. Enough talking, here's most of the code you'll need to populate your task forms with data from your original SPListItem.File:
public void FormEvents_Loading(object sender, LoadingEventArgs e)
{
string xmlSource = this.DataSources["ItemMetadata"].CreateNavigator().SelectSingleNode("/z:row/@ows_OrigXMLSource", NamespaceManager).Value;
XmlDocument nodeDoc = new XmlDocument();
nodeDoc.LoadXml(xmlSource);
foreach (XmlNode thisNode in nodeDoc.DocumentElement.ChildNodes)
{
try
{
if ((thisNode.LocalName.StartsWith("rpt")) || (thisNode.LocalName.StartsWith("gp")))
{
//we need to delete the default one Infopath adds in
this.MainDataSource.CreateNavigator().SelectSingleNode("/my:MainNode/my:" + thisNode.LocalName + "/my:" + thisNode.FirstChild.LocalName, NamespaceManager).DeleteSelf();
//this is repeating so replace the whole tree
this.MainDataSource.CreateNavigator().SelectSingleNode("/my:MainNode/my:" + thisNode.LocalName, NamespaceManager).AppendChild(thisNode.InnerXml);
}
else
{
if ((thisNode.InnerXml != ""))
{
//we must check for a nil attribute, if it is there, you will get a non-datatype error trying to set the value
XPathNavigator nodeNav = this.MainDataSource.CreateNavigator().SelectSingleNode("/my:MainNode/my:" + thisNode.LocalName, NamespaceManager);
if (nodeNav.MoveToAttribute("nil", "http://www.w3.org/2001/XMLSchema-instance"))
{
nodeNav.DeleteSelf();
}
nodeNav.SetValue(thisNode.InnerXml);
}
}
}
catch { }
}
}
Note that in my example, I am sending over the actual xml of the original list item file as a string value in the ItemMetadata. This is simple enough to do by just pushing this as one of the ExtendedProperties in the task object. Additionally, I am using the identifer “rpt” and “gp” to indicate whether a particular node is a repeating element or a group element. My original idea had been just to delete the entire “MainNode” and replace it with the corresponding one from my original item file; however, this just gives errors all over the place including, after you think you've matched the actual XML precisely, the infamous and non-descriptive “Non data type errors“ message. You must also include that nil attribute check to avoid errors in that area (thanks to Bruce on that one). Play with it, change it, make it yours and let me know how it goes.
After finally getting this working beautifully, the next task was to then be able to update the original list item XML so that any updates made in the various task forms would be visible to the user(s) if they opened the original item again. Easier than it sounds. My first thought, open the file, stream the binary and then load that into an XmlDocument object. Do any updating to the XmlDocument object and stream it back using SaveBinary to update the original item. No amount of playing with this would work. I figured it had something to do with the code I was using to update the XmlDocument object so, of course, I spent a couple hours fiddling with it only to always be met with the lovely non data type error each time I tried to view the original list item. Frustrated and swearing at Infopath, I finally stepped back out of the method altogether, breaking it down to the bare bits:
A. Opening the file binary -- works
B. Opening the file binary, Saving binary -- works
C. Opening the file binary, loading the xml into an XmlDocument object, doing absolutely nothing, streaming this back to a SaveBinary operation -- partially worked. By partially I mean that the Infopath client could now open the original list item but the item could not be opened in the browser.
Many, many viewings of the XML files.... one that would open, one that wouldn't. I couldn't find any differences. Line by line I would go. Everything looked intact. Finally I pinged another consultant who recommended I use this cool program that would compare two XML files. No, no actual differences except that the document which we pushed through an XmlDocument object now had CRLF between empty nodes. Wala -- there was the problem. So, if you are going to manipulate your list item and that list item is an Infopath form, you must do the following:
SPWeb thisWeb = new SPSite(workflowProperties.SiteId).OpenWeb(workflowProperties.WebId);
SPListItem thisItem = thisWeb.Lists[workflowProperties.ListId].GetItemById(workflowProperties.ItemId);
//to make it safe
thisItem.File.CheckOut();
byte[] fileBytes = thisItem.File.OpenBinary();
MemoryStream itemStream = new MemoryStream(fileBytes);
XmlDocument itemSource = new XmlDocument();
XmlNamespaceManager nm = new XmlNamespaceManager(itemSource.NameTable);
nm.AddNamespace("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2006-10-09T23:29:27");
itemSource.Load(itemStream);
//now do whatever processing you want to do on your XML
string stringSource = itemSource.OuterXml;
MemoryStream backOut = new MemoryStream(Encoding.UTF8.GetBytes(stringSource));
thisItem.File.SaveBinary(backOut);
thisItem.File.CheckIn("We were safe!");
By using a string value instead of saving the XmlDocument object with a .Save operation, the CRLFs and whitespace will be stripped out. Saving this back to the orignal list item file will make it valid for both the Infopath client and the browser. To me, this sounds like a bug so I have let the Infopath team know about it.
In Sharepoint workflows, earlier I bemoaned the “User ID“ column in the workflow history list. This now works perfectly, you must create a new field for the User object in the Log To History List object. You assign the user id to the executor of the task (or whatever you are logging) and then that user will show up in the history list for the UserID column. If you leave the log object's user field to -1, it will always show up as System which is completely useless.
Now that I've moved past all of my Infopath task issues, I seem to be going strong on developing my Sharepoint workflow. I will be interested to hit some people up next week in Vegas with questions and comments and maybe find others who are neck deep into this and garner the value of personal experience.
My next task: just how enterprise is Enterprise Document Management under MOSS 2007 ...