Welcome to Office Zealot Sign in | Join | Help

Julie's Office Dev Blog

Sorta like yoga... just less flexible.
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  ...

Posted: Tuesday, October 31, 2006 5:09 PM by jkremer

Comments

Anonymous said:

Not sure if you ment to say SH1T

The wonderful Mr. Sanderman was kind enough to shit the pain
# November 1, 2006 3:51 AM

GregS442 said:

How exactly do you assign the User to the executor of the task?  I've taken out the -1 and binded a new property to the UserID in the log history activity and in the method invoking I am do this which is not working.  tempReject_UserID is my new UserID property

tempReject_UserID.Equals(onTaskChanged1.Executor);

Any help would be appreciated.

# January 16, 2007 11:55 AM

jkremer said:

Hi Greg,

You would need to get the UserID out of the Executor and then assign one to the other...  I will post code shortly.

# January 17, 2007 5:33 PM

omar said:

Hello Julie, great blog. I'm trying to write back the xml of a browser enabled infopath form to the form library. Everything seems to be working perfectly, however the problem you're describing in point 3, i'm having in point 2. I'm deserializing the xml to the infopath class generated from xsd, modifying the data and then saving binary.

Problem occurs when i try to open the form in the browser, however if i open it in infopath client, it requires the template, if i specify it manually the modifications show in the client.

Would this possibly be related to the Processing instructions of the xml source? It's kind of weird why i would need them granted the xml is located in the forms library which concurently should use this template by default.

Awaiting your advice.

Regards,

Omar

# January 25, 2007 7:10 AM

jkremer said:

Hi Omar,

I would guess if you are seeing this same behaviour it has to do with the line feeds loading into an XMLDocument object creates.  You must save it back to a plain text file in order to get around this issue.  I haven't seen it require the template though with this issue so I'd need a bit more background on how you are doing what you are doing.  Please feel free to email me ... julie dot kremer at neudesic dot com

# January 27, 2007 9:13 PM

kiran bellala said:

Hello Julie,

A wonderful blog. After working with MOSS and developing custom workflows for about 4 months , I am convinced enough that MOSS+WinFX workflows are more suited for document management.

But in reality most of the clients wants to build workflows to automate their business processes.

After looking at your blog, I assume that your workflows start after filling in a InfoPath form.

In the task forms, we would see a link to the original InfoPath request form itself. How are you disabling the Submit button on the original request form after the request is submitted?

Thanks a lot.

# February 12, 2007 11:05 AM

jkremer said:

Hi Kiran,

I think MOSS workflows can be used for more than just document management.  Workflows that are predominately task based are a great opportunity for Sharepoint workflows.  Also, a lot of clients are interested in surfacing workflow through Sharepoint because it is an easy and available workflow host for the WF platform.

Most of my workflows are kicked off based on the addition of an item to a Sharepoint library.  Most of the time this is an Infopath form (but in some cases Word documents or other types of content).  To solve the problem that you are discussing with the "submit" button, I am using a status column in the original Infopath form.  Once the form is submitted, I change the status column to some value and then show or hide the submit button (usually I change it out with a close button).

# February 12, 2007 3:43 PM

Steven_Inter said:

I am attempting to do similar things with regard to InfoPath forms returning repeating fields.

I have attempted to follow this code but am not strong with some of the coding examples.

Was wondering what some of the local variables used, such as "this.DataSource" was referencing or assigned to?

Also, the "StartsWith("rpt")", does this mean that in InfoPath, you named your repeating fields "rptName" for example?

I have been working on this for a fair period of time and this is the only site that seems to talk about this issue.  Any help would be appreciated, or even a pointer to help sites would be great.

# June 18, 2007 4:11 AM

hloganathan said:

Dear Julie,

This post is wonderful and solved my problem exactly. But I have one query to you. I have copied the repeating section from a request form to a task form. But what should I do if I need to copy the changes to the repeating section from one task form to another.

Currently I'm copying the changed XML in an text field and it will be passed to the task form as the extendedproperties. This property will be accessed again on the formload event to show the changes in the previous task form.

Is it any better way to do this? Any comment will be more helpful.

Regards,

Logan

# April 16, 2008 3:03 AM

andyb said:

Julie - can you elaborate on the following:

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.

How do you pass the original list item file xml as a string. I have tried using workflowProperties.Item.Xml.ToString() or workflowProperties.Item.File.Item.Xml.ToString() but this does not contain the form data.

# August 17, 2008 6:55 PM
Anonymous comments are disabled