Thursday, February 4, 2010

Submit a new InfoPath form to a SharePoint Form Library Programmatically

Let me first start out by saying that if anyone has a better way to do this I am all ears. As far as I’m concerned this isn’t an ideal solution, but after scouring the web I simply could not find a different approach.

Requirements
The ability to programmatically add a new InfoPath form to a SharePoint Form Library using one of the content type templates that have been defined in the same Form Library.

Summary
The reason I need this functionality is because I have developed a very complicated and interactive Silverlight form that will have a number of dependent InfoPath forms. I needed a way to generate these on the fly whenever a new record was created and then to be able to reference them. Adding a file to a Form Library is pretty trivial, but getting an InfoPath template from a Form Library is not out of the box. This part was critical as the template has the ability to be updated at any time and I wanted to make sure I was always using a valid one.

A special thanks to Mike Hodnick for providing the code for retrieving the InfoPath template.
Programmatically create an InfoPath form instance from XSN template

UPDATE: It turns out this solution will only work with the root site and not other site collections in the same farm. Fortunately, there is a work around. Keep these steps in mind when following these instructions.
1. Encapsulate the code from GetInfoPathTemplate.aspx into a web part and deploy it to SharePoint.
2. Install the web part on a web part page in the same site collection where the forms will be located.
3. Update the web service to include a parameter for the web part page url.

Create Web Service Project
1. Create an “ASP.NET Web Service Application” project in Visual Studio 2008.
2. Make sure that the assembly is signed with a strongly named key. (Project –> Properties –> Signing –> Sign the assembly)
3. Add a reference to Microsoft.SharePoint (C:\Program Files\Common Files\microsoft shared\web server extensions\12\ISAPI\Microsoft.SharePoint.dll)
4. Add a reference to Microsoft.Office.InfoPath and Microsoft.InfoPath.Server (c:\Program Files\Microsoft Office Servers\12.0\Bin)
5. Add Web Form to the project. I have called mine GetInfoPathTemplate.aspx.
6. Add a single PlaceHolder control to the page in design view. This will be used to instantiate an InfoPath form and will allow us to retrieve the template xml.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="GetInfoPathTemplate.aspx.cs" Inherits="Acme.Services.InfoPath.GetInfoPathTemplate" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<
html xmlns="http://www.w3.org/1999/xhtml">
<
head runat="server">
<
title></title>
</
head>
<
body>
<
form id="form1" runat="server">
<
div>
<
asp:PlaceHolder ID="formHostPlaceholder" runat="server" />
</
div>
</
form>
</
body>
</
html>

7. Add the code behind for the page. You will see that it takes a query string called “templateUrl”. (Note: I haven’t add exception handling yet.)

using System;
using System.IO;
using Microsoft.Office.InfoPath.Server.Controls;

namespace Acme.Services.InfoPath
{
public partial class GetInfoPathTemplate : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.QueryString["templateUrl"] != null)
{
XmlFormView formView = new XmlFormView();
formView.Initialize += new EventHandler<InitializeEventArgs>(formView_Initialize);
string url = Request.QueryString["templateUrl"].ToString();
this.formHostPlaceholder.Controls.Add(formView);
formView.XsnLocation = url;
}
}

void formView_Initialize(object sender, InitializeEventArgs e)
{
XmlFormView formView = sender as XmlFormView;
Stream stream = formView.XmlForm.Template.OpenFileFromPackage("template.xml");
StreamReader reader = new StreamReader(stream);
string xml = reader.ReadToEnd();
this.Response.Clear();
this.Response.ContentType = "text/xml";
this.Response.Write(xml);
this.Response.End();
}
}
}

8. Open up the code behind for the web service and add code for retrieving template names, updating the template xml, and submitting to a SharePoint Form Library.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Services;
using System.Xml.Linq;
using Microsoft.SharePoint;

namespace Acme.Services.InfoPath
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class Service : System.Web.Services.WebService
{
[WebMethod]
public bool SubmitInfoPathForm(string templateUrl, string formItemUrl)
{
bool submitted = false;
string infoPathTemplateUrl = string.Empty;
try
{
WebClient client = new WebClient();
client.Credentials = CredentialCache.DefaultCredentials;
infoPathTemplateUrl = GetInfoPathTemplateUrl() + "?templateUrl=" + templateUrl;
Stream stream = client.OpenRead(infoPathTemplateUrl);
StreamReader reader = new StreamReader(stream);
string formXml = UpdateTemplate(templateUrl, reader.ReadToEnd());
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] bytes = encoding.GetBytes(formXml);
using (SPSite site = new SPSite(templateUrl))
{
using (SPWeb web = site.OpenWeb())
{
web.Files.Add(formItemUrl, bytes);
submitted = true;
}
}
}
catch (Exception ex)
{
throw ex;
}
return submitted;
}

[WebMethod]
public string[] GetInfoPathTemplates(string webUrl, string listName)
{
List<string> templates = new List<string>();
using (SPSite site = new SPSite(webUrl))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists[listName];
SPContentTypeCollection contentTypes = list.ContentTypes;
foreach (SPContentType contentType in contentTypes)
{
if (contentType.DocumentTemplate.EndsWith(".xsn"))
{
templates.Add(contentType.DocumentTemplate);
}
}
}
}
return templates.ToArray();
}

private string GetInfoPathTemplateUrl()
{
return HttpContext.Current.Request.Url.ToString().Replace("Service.asmx", "GetInfoPathTemplate.aspx");
}

private string UpdateTemplate(string templateUrl, string templateXml)
{
XDocument xDocRoot = XDocument.Parse(templateXml);
IEnumerable<XNode> nodes = xDocRoot.Nodes();
foreach (XNode node in nodes)
{
if (node.ToString().Contains("mso-infoPathSolution")) //Lame, but couldn't find any other way.
{
if (!((XProcessingInstruction)node).Data.Contains("href="))
{
((XProcessingInstruction)node).Data = ((XProcessingInstruction)node).Data + " href=\"" + templateUrl + "\"";
}
break;
}
}
return xDocRoot.Declaration + xDocRoot.ToString();
}
}
}

9. Create a folder called “InfoPathServices” at the level of your SharePoint bin directory and add your web service “Service.asmx” and web form “GetInfoPathTemplate.aspx”. (C:\inetpub\wwwroot\wss\VirtualDirectories\80)

image

10. Register your web service assembly in the GAC and take note of the Public Key Token. (Start –> Run –> assembly)
11. Open the web.config and add a new SafeControl and assembly tag with the Public Key Token in the previous step. (C:\inetpub\wwwroot\wss\VirtualDirectories\80)
<configuration>
<SharePoint>
<SafeControls>
<SafeControl Assembly="Acme.Services.InfoPath, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxx" Namespace="Acme.Services.InfoPath" TypeName="*" Safe="True" />
</SafeControls>
</SharePoint>
<system.web>
<compilation>
<add assembly="Acme.Services.InfoPath, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxx" />
</compilation>
</system.web>
</configuration>

Testing

1. Template Retrieval Testing - Test the web application by opening the URL with the query string to a valid template url.

http://[portalname]/InfoPathService/GetInfoPathTemplate.aspx?templateUrl=http://[portalname]/[form libraryname]/Forms/[reporttemplatename].xsn

It should look something like this:


image


2. For the rest, you can simply create a Console Application that references the web service. Here is an example I have provided. You will need four parameters:

Web Url – Base URL where your list is located.
List Name – Name of your form library.
Content Type Name – The name of your report template.
List Item Name – This is the name that you will be giving your report.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Microsoft.SharePoint;
using Acme.Services.InfoPath.Tester.InfoPathService;

namespace Acme.Services.InfoPath.Tester
{
class Program
{
static void Main(string[] args)
{
string webUrl = @"http://[portalname]/[teamsitename]/";
string listName = "[formlibraryname]";
string contentTypeName = "[reportname]";
string listItemName = Guid.NewGuid().ToString();
try
{
SubmitInfoPath(webUrl, listName, contentTypeName, listItemName);
}
catch (Exception ex)
{
//todo: handle exception
}
}

static void SubmitInfoPath(string webUrl, string listName, string contentTypeName, string listItemName)
{
string templateUrl = GetTemplateUrl(webUrl, listName, contentTypeName);
string itemUrl = string.Format(webUrl + listName + "/" + listItemName + ".xml");
if (!string.IsNullOrEmpty(templateUrl))
{
Service sc = new Service();
sc.Credentials = CredentialCache.DefaultNetworkCredentials;
sc.SubmitInfoPathForm(templateUrl, itemUrl);
}
else
{
throw new Exception("Content type does not exist.");
}
}

static string GetTemplateUrl(string listUrl, string listName, string contentTypeName)
{
string templateUrl = string.Empty;
Service sc = new Service();
sc.Credentials = CredentialCache.DefaultNetworkCredentials;
string[] templates = sc.GetInfoPathTemplates(listUrl, listName);
templateUrl = (from t in templates where t.Contains(contentTypeName) select t).Single();
return templateUrl;
}

public static string[] GetInfoPathTemplates(string webUrl, string listName)
{
List<string> templates = new List<string>();
using (SPSite site = new SPSite(webUrl))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists[listName];
SPContentTypeCollection contentTypes = list.ContentTypes;
foreach (SPContentType contentType in contentTypes)
{
if (contentType.DocumentTemplate.EndsWith(".xsn"))
{
templates.Add(contentType.DocumentTemplate);
}
}
}
}
return templates.ToArray();
}
}
}
Assuming you did everything right, you should start seeing items in your form library!
image

No comments:

Post a Comment