SharePoint: Programmatically Creating InfoPath Form Instance Documents Part 2 of 2


Continuing on from part 1 of this 2 part series, we discovered the hows and whys of creating InfoPath form instance documents.

This post brings together that information and demonstrates the code steps required to create an InfoPath form instance document and post it to a SharePoint document library.

First we’ll grab the SharePoint content type which represents the InfoPath form template for which we want to create an instance document.

From the content type we’ll get the document template URL value, this is the URL to the form template (.XSN) file.

// get CT
var ct = from SPContentType wct in web.AvailableContentTypes
			where wct.Name.Equals(contentTypeName, StringComparison.OrdinalIgnoreCase)
			select wct;
if (ct.Count() < 1)
	throw new Exception(string.Format("Content Type {0} not found in Web {1}", forContentTypeName, web.Site.Url));
var docTempl = ct.First().DocumentTemplateUrl;

Next we’ll get the Forms XSN SPFile referenced by the content types document template url.

public static SPFile GetFormTemplateFile(SPWeb web, string xsnFormTemplateUrl)
{
	try
	{
		var xsnFile = web.GetFile(xsnFormTemplateUrl);
		return xsnFile;
	}
	catch (Exception)
	{
		throw new Exception(string.Format("Unable to load form template {0} in Web {1}", xsnFormTemplateUrl, web.Site.Url));
	}
}

Now we have the form XSN file, we’ll open it using our InfoPath form grocker class, and extract the form manifest (.XSF) and template xml files.

private void InitialiseFromFormTemplate(SPFile xsnFormTemplate)
{
	using (var xsnFormStream = new MemoryStream(xsnFormTemplate.OpenBinary(SPOpenBinaryOptions.SkipVirusScan)))
	{
		var xsnForm = new InfopathFormGrocker(true);

		// get the template Xml file
		xsnForm.ExtractComponent(xsnFormStream, "template.xml")
		TemplateXml = xsnForm.ComponentContent.DocumentElement.OuterXml;

		// get the manifest (.XSF) file
		xsnForm.ExtractComponent(xsnFormStream, "manifest.xsf")
		var formManifest = xsnForm.ComponentContent.DocumentElement;

		........
	}
}

Next we need to get the form metadata used to create the form instance processing instructions (mso-infoPathSolution and mso-application) and these values are located in the manifest (.XSF) file as attributes on the root document element. We also need to get the full URL to the form template (.XSN) file.

In order to query the form manifest XML file (.XSF) we’ll need to create an XmlNamespaceManager object.

public static XmlNamespaceManager CreateNamespaceManager(XmlDocument document)
{
	if (document == null) throw new ArgumentNullException("document");
	if (document.DocumentElement == null) throw new ArgumentNullException("document", "The root document element is null!");

	var ns = new XmlNamespaceManager(document.NameTable);
	foreach (XmlAttribute xatt in document.DocumentElement.Attributes)
	{
		var prefixPair = xatt.Name.Split(new[] {':'}, StringSplitOptions.RemoveEmptyEntries);
		if (prefixPair.Length < 1) continue;
		if (!prefixPair[0].Equals("xmlns", StringComparison.OrdinalIgnoreCase)) continue;

		var prefix = prefixPair.Length == 2
							? prefixPair[1]
							: string.Empty;
		var uri = xatt.Value;
		ns.AddNamespace(prefix, uri);
	}
	return ns;
}

 

		// get form metadata values
		var ns = CreateNamespaceManager(formManifest);
		Name = formManifest.DocumentElement.GetAttribute("name", "");
		SolutionVersion = formManifest.DocumentElement.GetAttribute("solutionVersion", "");
		ProductVersion = formManifest.DocumentElement.GetAttribute("productVersion", "");

		// get the form template (.XSN) file url from the SPFile itself
		HRef = xsnFormTemplate.Item["ows_EncodedAbsUrl"].ToString();

		// get the (english) form (display) name
		var node = formManifest.SelectSingleNode("//xsf2:solutionPropertiesExtension[@branch='share']/xsf2:share", ns);
		if (node != null && (node is XmlElement))
			FormName = (node as XmlElement).GetAttribute("formName");

Now we have all the pieces, we need to assemble them together to create the final form instance document, to recap, the pieces we need are;

  1. Form metadata values for;
    • Form name (the namespace URI of the form schema)
    • Solution version
    • Product version
    • HRef (URL of form template .XSN file)
    • PI version
    • ProgId
    • Version ProgId
  2. Form template Xml (the Xml document resulting from the form template schema)
public XmlDocument CreateFormInstanceDocument()
{
	var doc = new XmlDocument();
	doc.Load(new XmlTextReader(new StringReader(TemplateXml)));

	// first remove the PI nodes if they're already present (which they shouldn't!)
	var piNodes = doc.SelectNodes("/processing-instruction()");
	if (piNodes != null)
	{
		foreach (XmlNode piNode in piNodes)
		{
			if (	piNode.LocalName == "mso-infoPathSolution" ||
					piNode.LocalName == "mso-application" ||
					piNode.LocalName == "MicrosoftWindowsSharePointServices")
				doc.RemoveChild(piNode);
		}
	}

	// create PI values
	var mso_infoPathSolution = string.Format("name=\"{0}\" solutionVersion=\"{1}\" productVersion=\"{2}\" PIVersion=\"{3}\" href=\"{4}\"",
													Name,
													SolutionVersion,
													ProductVersion,
													"1.0.0.0",
													HRef);
	var mso_application = string.Format("progid=\"{0}\" versionProgid=\"{1}\"",
													"InfoPath.Document",
													"InfoPath.Document.2");

	// add PIs to doc
	var pi = doc.CreateProcessingInstruction("mso-infoPathSolution", mso_infoPathSolution);
	doc.InsertBefore(pi, doc.DocumentElement);

	pi = doc.CreateProcessingInstruction("mso-application", mso_application);
	doc.InsertBefore(pi, doc.DocumentElement);

	return doc;
}

Notice that I’ve harcoded the values for;

  • PI version
  • ProgId
  • Version ProgId
  • In a production ready solution you’d probably want to store these values away somewhere, to support future InfoPath versions should they change.

    Finally we’ll take the XmlDocument we’ve created and publish it to a SharePoint document library, while doing this we’ll set no more SharePoint list item metadata than the Title.

    The SharePoint Xml parsing infrastructure will determine the correct content type the document item should be associated with, and any (promoted) property columns the form template was published with will be updated accordingly.

    var formInstanceDoc = CreateFormInstanceDocument();
    var metadata = new Hashtable
                    {
                    	{ "vti_title", "The Forms Title Value" }
                    };
    
    var list = web.Lists["Shared Documents"];
    var formBytes = Encoding.UTF8.GetBytes(formInstanceDoc.OuterXml);
    var documentFile = list.RootFolder.Files.Add("MyNewForm.xml", formBytes, metadata, true);
    

    Published by

    Phil Harding

    SharePoint Consultant, Developer, Father, Husband and Climber.

    2 thoughts on “SharePoint: Programmatically Creating InfoPath Form Instance Documents Part 2 of 2

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out /  Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out /  Change )

    Connecting to %s

    This site uses Akismet to reduce spam. Learn how your comment data is processed.