My career in technology

Taking a break from talking about the merely difficult, I want to talk about doing the impossible.

“If you’ve done six impossible things this morning, why not round it off with breakfast at Milliways—the Restaurant at the End of the Universe!” – The Restaurant at the End of the Universe, by Douglas Adams

By design, InfoPath will only let you publish a form to one SharePoint location. Or, rather, to be specific, when you have published a form template to a SharePoint library, if you fill out the form, that data file will only point back to the library from which it was created to get its form template. The reason behind this is to provide a central location for the form template to reside. When a user opens the form, the layout of the form and its beahvior is determined by the template. If InfoPath can’t reach the template location, the form will not open and you will get an error:

“InfoPath cannot open the following file:
<(serverlocation)filename.xml>. The following form template for the form cannot be found: http://SharePoint/Site/List/Forms/template.xsn”

So, let’s say, for the sake of argument, that you have a requirement that the form needs to be able to be opened from two different locations that can’t talk to one another?


(Also, check out my no code solution)

Here is a scenario – two organizations have a need to share some internal form data, but there is no extranet set up to do the sharing, and for reasons of compliance, security, or business practice can’t open up their internal networks to each other. On both sides, the data needs to reside on a SharePoint site. The process starts at one network, then the form data is sent to the other network for completion.

How does the form data know where to get the form template? What is it that sets the location of the SharePoint site that the form resides in?

This is the first line in the InfoPath form data XML file:

<?xml version="1.0"?><?mso-infoPathSolution name="urn:schemas-microsoft-com:office:infopath:FormName:-myXSD-YYYY-MM-DDTHH-MM-SS" solutionVersion="1.0.0.364" productVersion="12.0.0.0" PIVersion="1.0.0.0" href="http://SharePoint/Site/List/Forms/template.xsn"?><?mso-application progid="InfoPath.Document" versionProgid="InfoPath.Document.2"?>

There are a few pieces in here in which you would see your own values:
Form-Name: This is the Title of your Form (not the filename), with dashes replacing spaces.
YYYY-MM-DDTHH-MM-SS: This is the creation date of your form, YYYY = year, MM = month, DD = day, HH= hour, MM = is minute, SS = seconds. (The T is the capital letter T).
SharePoint/Site/Library/: Your SharePoint URL, including the site (and subsites, if any) and the Library in which the InfoPath form was published.

Together, all of the above is the Processing Instructions for the InfoPath Form. It tells the file what program to open up in, and where to get the form template.

I shouldn’t have to say this, but an XML file is simply a text file, and editing a text file is pretty darned easy. Once the file is sent from the initial domain, simply substitute the URL of the Library in the second domain for that of the first.

Yeah, but who wants to manually edit a text file every time you have to upload the form to the new Library? I mean, uploading the document to the Library is tedious enough, right?

Once again, my friend the Event Handler is there to automate things that can and should be automated.

(Update: I’ve figured out a second way to migrate the form using no code.)

using System;
using System.Data;
using System.Collections.Generic;
using System.Configuration;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.XPath;
using Microsoft.SharePoint;

namespace FormTransferEH
{
    public class FormTransferEventHandler : SPItemEventReceiver
    {
        public override void ItemAdded(SPItemEventProperties EventProperties)
        {
            if (EventProperties.ListTitle == "Form Upload")
            {
                //We are only implementing this set of commands on the Request Upload list,
                //so if it is running on any other list, skip it.

Notice that I am using the ItemAdded() event to trigger the actions, and that I only want to run these actions on documents uploaded to the “Form Upload” Library.

                // Retrieve the InfoPath form that was just added
                byte[] xmlFormData = null;
                SPFile file = EventProperties.ListItem.File;
                xmlFormData = file.OpenBinary();

Declare the byte array, grab the uploaded file and perfom OpenBinary() on it, dumping the bytes into the byte array.

                string xml = Encoding.UTF8.GetString(xmlFormData);
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(xml);

Take the byte array and encode as UTF8, then load it into an XMLDocument.

                // read processing instruction from document
                XmlProcessingInstruction pi = (XmlProcessingInstruction)doc.SelectSingleNode("/processing-instruction(\"mso-infoPathSolution\")");

Find the Processing Instruction.

                // update processing instruction value
                pi.Value = "updated value";

Update the value of the processing instruction to your new value. pi.Value returns this part of the first line in the XML file:

name="urn:schemas-microsoft-com:office:infopath:FormName:-myXSD-YYYY-MM-DDTHH-MM-SS" solutionVersion="1.0.0.364" productVersion="12.0.0.0" PIVersion="1.0.0.0" href="http://SourceSharePoint/SourceSite/SourceList/Forms/template.xsn"

So, instead of pi.Value = “updated value”, I would recommend

pi.Value = pi.Value.Replace("http://SourceSharePoint/SourceSite/SourceList/", "http://DestinationSharePoint/DestinationSite/DestinationList/")

This simply changes the template location from the original domain to the template location in the new domain.

                xmlFormData = Encoding.Default.GetBytes(doc.OuterXml);
                file.SaveBinary(xmlFormData);
                file.Update();
            }
            base.ItemAdded(EventProperties);
        }

    }
}

This was the part that was throwing me – how to write the modified XML file back to the SPFile object. Once I finally got that figured out, it was done!

Then, regardless of whether or not the SPItemAdded event was on the “Form Upload” Library, the base.ItemAdded() needs to be called.

Finally, using a simple workflow, I move the document from the “Form Upload” Library to the Library where the forms will be kept.

Yes, Virginia, you can host Infopath forms in two locations (but, no, there is no sanity clause…)

More posts about InfoPath.

More posts about SharePoint.

Comments on: "Publish an InfoPath Form to Multiple SharePoint Sites" (17)

  1. […] In addition to my method of using an Event Handler to make it possible to host an InfoPath file in multiple locations, I have figured out a way to host an InfoPath form in multiple locations and be able to transfer form data from one location to the other and be able to open the forms on each site, without writing a single line of code. […]

    • My post – A No-Code Solution to Host an InfoPath Form in Multiple SharePoint Sites – provides a way to migrate the data from one library to another using no code. So if you aren’t a programmer or can’t deploy a solution to your SharePoint site, that solution may be better for you.

      The downsides to the no-code solution are 1) you would have to update the process every time you changed the data structure of the form, and 2) you have to make all of your data editable outside the logic and rules enforced by the form. With the event handler soution, you only need to make a change if the source or destination library location changes.

  2. […] InfoPath to SharePoint By Jim Adcock My article, “Publish an InfoPath Form to Multiple SharePoint Sites”, and its no-code companion, “A No-Code Solution to Host an InfoPath Form in Multiple […]

  3. […] Publish an InfoPath Form to Multiple SharePoint Sites May 20103 comments 3 […]

  4. […] Publish an InfoPath Form to Multiple SharePoint Sites […]

  5. […] year, my post “Publish an InfoPath Form to Multiple SharePoint Sites” passed my previous top post (“Tweaking IE 8“) and hit 1,000 page views.  So far […]

  6. […] Publish an InfoPath Form to Multiple SharePoint Sites […]

  7. santosh bhagat said:

    Export InfoPath form to a spreadsheet by code behind

    • I’m not sure if you are asking me how to do this, or if you are saying that my solution helped you figure out how to do it, or what. Clarify please?

  8. […] Publish an InfoPath Form to Multiple SharePoint Sites […]

  9. Jim thanks for the post. This looks like it replaces the entire processing instructions. How do I just replace the href part of it?

    • See this part of the instrucitons, that shoud get you where you want to go…

      So, instead of pi.Value = “updated value”, I would recommend

      pi.Value = pi.Value.Replace(“http://SourceSharePoint/SourceSite/SourceList/”, “http://DestinationSharePoint/DestinationSite/DestinationList/”)

      This simply changes the template location from the original domain to the template location in the new domain.

      Let me know if you still have questions or problems!

    • Nevermind. I see what can be done.
      pi.Value.Replace(“href=”,”href=newaddress”)

      But it’s not a sure way to replace it as it’s more of a text replace rather than a node replacement. If the intended value is not there the new value won’t get replaced. Is there a better way of doing this?

      • There should be, but I didn’t take advantage of it. The requirement I was given had specific domains and locations where the original form would be published and the copy would be hosted, so the text replacement was the easiest to accomplish.

        Some quick research to see what options are available to address the attribute for that node/element yielded this, which may be helpful: http://stackoverflow.com/questions/3004587/how-to-get-attribute-value-using-selectsinglenode

        It may at least set you off in the right direction. Once you have it figured it out, I encourage you to post the solution here (or post it on your own blog – if you have one – and provide a link)!

      • After searching and reading some more. People have either recapture the entire PI section and then adding what they want, then recreate the entire section again or they do a replace as you just did here. However, here is a post that uses the ReplaceTemplateReference() function that replace the href when that key word is found rather than the entire match of the url.

        How to update all library’s InfoPath forms after relocation

      • Thanks for the update, I appreciate the link reference!

Leave a comment