Wednesday, 1 July 2009

InfoPath Custom Save Action: Save as Draft and Avoid Validation

Recently i’ve been working on a project that has required a user to be able to save a form to SharePoint as they are working on it, a kind of draft. The only way to do this is to allow the user to use the standard ‘Save’ function on the toolbar (in IP client or browser). This offers the user the option to choose a filename – fine if there’s going to be 10 forms in the library, but if there are 000’s then users are going to be overwriting each other’s work.

First step was to set up a Submit Data Connection to the SharePoint library. This is good because I can generate a unique filename and post the document there. However, since the connection is a ‘Submit’, the form will not allow you to run it until all validation errors are cleared… no good for draft forms.

Next step was to jump into code behind - clear the validation errors in the collection, then submit the form:
This works fine, BUT – if the user wants to save a draft and continue working, all the validation has been basically disabled in the form until it is reloaded. SO: final solution was to manually write the xml file into the forms library. This was actually less hairy than it sounds. It consists of 2 methods in code behind – 1 to process a HTTP PUT command, and one to basically call it. The web request method is pretty generic: url: The full URL of the file (http://sharepoint/forms/myform123.xml)

header: The header information in an infopath file (the xml declaration and processing instructions). The method takes the header, selects the form data from the root node (in this case /my:Application), and builds a string before pushing that through the web request.

private System.Net.WebRequest BuildRequest(string url, string header)
string xmlString = header;

xmlString += MainDataSource.CreateNavigator().SelectSingleNode("/my:Application", NamespaceManager).OuterXml;

//now send the web request to load the document
System.Net.WebRequest request = System.Net.WebRequest.Create(url);

request.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
request.Method = "PUT";

System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();

byte[] buffer = enc.GetBytes(xmlString);

using (Stream stream = request.GetRequestStream())
stream.Write(buffer, 0, buffer.Length);
return request;

The following method just sets up the header (copied from a locally saved infopath form), and calls the web request method. Note: for a filename, I am using a field in my form called ‘GUID’, so I just select that from the form and build my url string with it.

public void SaveAndContinue()
string guid = MainDataSource.CreateNavigator().SelectSingleNode("/my:Application/my:Logic/my:GUID", NamespaceManager).Value;
string url = "" + guid + ".xml";
string relativeUrl = "/ChamoisPOC/" + guid + ".xml";
string header = @"<?xml version='1.0' encoding='UTF-8'?>
<?mso-infoPathSolution name='urn:schemas-microsoft-com:office:infopath:MasterSipp:-myXSD-2009-07-21T08-09-49'
solutionVersion='' productVersion='' PIVersion=''
<?mso-application progid='InfoPath.Document' versionProgid='InfoPath.Document.2'?>"

System.Net.WebRequest request = BuildRequest(url,header);

//request.Credentials = objCredentials;
System.Net.WebResponse response = request.GetResponse();
StreamReader rdr = new StreamReader(response.GetResponseStream());
string htmlresponse = rdr.ReadToEnd();

//check item was added
int itemID = CheckItemExists("FileLeafRef", guid + ".xml", "Chamois-POC");
if (itemID == -1)
throw new ApplicationException("Document has not been loaded. See server response\n" + htmlresponse);

(In production, you’d also want to call the GetListItems webservice to ensure it was saved correctly)

So, after being tied up to whichever event you choose (i’m saving in the background as the user fills out the form) this should quietly dump the xml file into a library. For some reason the library doesn’t recognise the xml as being an InfoPath file, so to open the form again you’ll need to send the user to the FormServer.aspx?xmllocation=http://sharepoint/forms/123456.xml page, and it will work fine.

If you publish the form as administrator approved, and apply the content type to the library you’re saving into, it will automatically populate the promoted properties as it saves. Lucky, eh…
Hopefully this makes sense – I searched for a neater way of doing this for ages, but i’ve found this approach to be the most manageable.