Click here to Skip to main content
Click here to Skip to main content

Empowering programming in Microsoft Office Infopath

, 25 Nov 2013
Rate this:
Please Sign up or sign in to vote.
Get a way to create and develop so much better and faster Infopath based programs.

Introduction

Often, Microsoft Office Infopath, or rather, the programming in Microsoft Office Infopath can be hard to manage depending on the logic business when they are implemented.

In order to get very handy and useful ways to encode and develop InfoPath’s Programs and common task on them, and also in great measure due to tedious and sometimes convoluted methods that have to be used, I’ve developed the classes in this file, which can be used through a *cs or an assembled DLL together to get the functionality easily accessible and usable. This can be very practical since you must be wary about many sorts of exception when even several simple tasks are done; there are different namespaces, errors management, long connection codes like webservice’s queries and information retrieval, etc.

The snippets code of the article are written in C#, and they're compatible with InfoPath forms 2007, 2010 and 2013.  

Using the code

1. How to increase the efficiency, easily and how to empower the development in Infopath forms.

When we have to deal with the information from different data sources, such a main data source or some secondary data sources, we have to use certain classes like XPathNavigator, which let us to get and fill said data sources.

To refer data sources, the XML interfaces (which the form code inherits from) supply an array called DataSources what includes all the sources references added through the Infopath itself, and whose name is the index to access to each one. Every element of the array is a DataConnection object type.

As an exception, to accessing main data source, the own code inherits the DataConnection behavior, so it's just necessary to execute CreateNavigator(), instead to index the element in the array to execute it.

First of all, newest Infopath Forms are developed in Visual Studio Tools For Applications, the program itself will add some necessary references when it is created, but if we do work in normal Visual Studio (it is weird for Infopath programs), we will have to add more references, totally these:

  • Microsoft.mshtml
  • Microsoft.Office.Infopath
  • Microsoft.Office.Interop.InfoPath
  • System.AddIn
  • System.AddIn.Contract
  • System.Core
  • System.Data
  • System.Data.DataSetExtensions
  • System.Xml
  • Microsoft.VisualStudio.Tools.Applications.Adapter
  • Microsoft.VisualStudio.Tools.Applications.Contract

these last ones preferably in its last version.

Create an XPathNavigator instance to navigate within the nodes, either to filling them or retrieving information.

Once an instance is ready to be used, if you try to access to a not existent node, an exception will occur and the main program will show an error over the user screen with the current exception, something the user shouldn't watch. This can be indeed catch, but the point is to automatize each error that could be shown to the user.

From a code Infopath is very common to set and get values of the nodes.

Related to this, obtaining a reference to xml node the way is:

xmlObject.SelectSingleNode(“node path”, Namespace) which returns another XPathNavigator (as the children nodes are retrieved too). On it, we can call the methods Value and SetValue() both to get and set the values respectively of the node.

The node may not exist, in which case an exception occurs if we try applying such methods to a null reference, so we can control by adding some methods:

private const string XML_NODE_ERROR = "XML_NODE_ERROR";
private const char SEPARATOR = '~';
public String getValue(XPathNavigator xml, String node)
{
    try
    {
        return xml.SelectSingleNode(node, NamespaceManager).Value;
    }
    catch (Exception)
    {
          // customize handle exception if appropiate
          return string.Concat(XML_NODE_ERROR, SEPARATOR.ToString(), node);
    }
}

public void setValue(XPathNavigator xml, string node, string value)
{
    try
    {
        xml.SelectSingleNode(node, NamespaceManager).SetValue(value);
    }
    catch (Exception) 
    {
        // customize handle exception if appropiate
    } 
}  

That way would simplify and reduce a lot of lines of code given the next sample situation:

These lines:

try
{
    string name = MainXml.SelectSingleNode("//Name", NamespaceManager).Value;
}
catch (Exception ex)
{
    // Node “Name” doesn´t exist
} 

are equivalent to this one:

string name = getValue(MainXml, "//Name"); 

Above we have seen how to simplify the control of the XML nodes. But, what's the matter when we try do requests and retrieving and exchanging information and something goes wrong? We also have to be careful with it. Imagine we have to set the value of the person’s country, that person appears within main data source. The country has to be retrieved via another data source. The query execution can throw three different exceptions, and then the node may not appear because of bad connection, inexistent data, so we have the next:  

XPathNavigator MainXml = CreateNavigator(), SecondXml = DataSources[”SecondXml”].CreateNavigator();
// We obtain the information from the second data source
// To do that, sometimes there is to provide the value for some required parameters
setValue(SecondXml, "//tns:Name", "Peter");
setValue(SecondXml, "//tns:Number", "2");

// Besides, we have to control the likely exceptions executing
// the query, this has to be done through the  datasource 
// Now you can see how many lines of code there are for each execution.

Boolean success = false;
try
{
    DataSources["SecondXml"].QueryConnection.Execute();
    success = true;
}
catch (System.Runtime.InteropServices.COMException COMex)
{ 
    // Handle the exception
}
catch (System.Net.WebException WEBex)
{
    // Handle the exception
}
catch (System.Exception ex)
{
    // Handle the exception
} 

// Here we can deal with the xml structure and exchanging
// information over the form and another xml structures
// Even, the success or fail from the previous execution there
// should be controlled in order to know if we have to execute
// the sentences come, that´s the reason we before declared the bool success

if (success)
{
    setValue(MainXml, "//Country", getValue(SecondXml, "//CountryReceived"));
}
try
{
    DataSources["SecondXml"].QueryConnection.Execute();
    success = true;
}
catch (System.Runtime.InteropServices.COMException COMex)
{
    // Handle the exception
}
catch (System.Net.WebException WEBex)
{
    // Handle the exception
}
catch (System.Exception ex)
{
    // Handle the exception
}

// Here we can deal with the xml structure and exchanging
// information over the form and another xml structures
// Even, the success or fail from the previous execution
// there should be controlled in order to know if we have to execute
// the sentences come, that´s the reason we before declared the bool success

if (success)
{     
    setValue(MainXml, "//Country", 
          getValue(SecondXml, "//CountryReceived"));
} 
Boolean success = false; 

We only have retrieved one simple data from a webservice, however, the code is big. When the operations number over the secondary data sources grows, the code’s extent grows much faster. On the other hand, we also have to specify the namespace in each data source used, which sometimes could change by certain definitions.

As it's known, the double bar (//) places the node reading in the first node with that name. And if we specify the NamespaceManager through the SelectSingleNode or similar methods (like Select for example) that require such parameter, we have not to specify the namespace itself in response nodes, but when we access to request nodes or in order to differentiate them or working with some xml structures where nodes can be mixed along them, we should specify the namespace, like we did at the previous samples: “//tns:node1”, what situates us in the first request node with name is equal to node1.

To avoid these complications we can enhance a query system, with a handy way to set request parameters, namespaces, do queries, with any possible exception well controlled.

A sort method like query (method, request values) which called to webservice, it retrieved the information and it filled the corresponding xml’s navigator would be a good choice, especially if we consider even the related exceptions will be catch all the time.

The name of the method of the webservice is the index of the data connection array, and the request values parameter is a map of pair values containing a node name and its corresponding value to realize the request. The map can be developed with a Dictionary.

private const string ERROR_WEBSERVICE = "Webservice error";
public void ExampleHandleException(string message)
{
    MessageBox.Show(message);
}

public Boolean query(String DataSourceString, Dictionary<String, String> map)
{
    return query(DataSourceString, map, string.Empty);
}

public Boolean query(String DataSourceString, Dictionary<String, String> map, String extraMessage)
{
    try
        {   
        XPathNavigator ws = DataSources[DataSourceString].CreateNavigator();
        foreach (KeyValuePair<string, string> mapValue in map)
        {
            String key = mapValue.Key;
            String[] recString = mapValue.Value.Split(SEPARATOR);
            String value = string.Empty;
            if (recString.Length > 1) value = recString[1];
            if (value.Equals(XML_NODE_ERROR))
            {
                // Here we use the custom error we did above to recognize                 
        //the error which has failed
                throw new System.Exception(
                  string.Concat("WebServiceError", DataSourceString, " – Node error: ", 
                  key, " trying to set the value", value, "\n\n", extraMessage));
            } 
            value = mapValue.Value;
            ws.SelectSingleNode(key, NamespaceManager).SetValue(value);                 
    }        
    catch(Exception ex)
    {        
        ExampleHandleException(string.Concat(ERROR_WEBSERVICE, ex.TargetSite.ToString(), 
          "on ", DataSourceString, "com.\n\n", ex.Message);
    }
    try     
    {
            // Query is executed        
        DataSources[DataSourceString].QueryConnection.Execute();        
        return true;        
    }    
    catch (System.Runtime.InteropServices.COMException ex)
    {
        ExampleHandleException(string.Concat(ERROR_WEBSERVICE, ex.TargetSite.ToString(), 
          "on ", DataSourceString, "com.\n\n", ex.Message, ex.ErrorCode.ToString());
        return false;
    }
    catch (System.Net.WebException ex)
    {
        ExampleHandleException(string.Concat(ERROR_WEBSERVICE, ex.TargetSite.ToString(), 
              "on ", DataSourceString, "com.\n\n" , 
              ex.Message, ex.ErrorCode.ToString());                            
        return false;
    }
    catch (System.Exception ex)
    {
        ExampleHandleException(string.Concat(ERROR_WEBSERVICE, ex.TargetSite.ToString(), 
          "on ", DataSourceString, "com.\n\n", ex.Message, ex.ErrorCode.ToString());                         
        return false;
    }
}

We’ve override the query method to be able to provide an extra message in certain situations.

Before nothing, we can instantiate a fast way to create dictionaries.

public Dictionary<String, String> d { get; set; }        
public Dictionary<String, String> dic()
{
    return new Dictionary<String, String>();
}

The query method let us become the code above to the next:

XPathNavigator MainXml = CreateNavigator(), SecondXml = DataSources["SecondXml"].CreateNavigator();
d = dic();

d.Add("//tns:Name", "2");
d.Add("//tns:Number", "2");

if (query("SecondXml", d)) //If the query goes all right
{
    setValue(MainXml, "//Country", getValor(SecondXml, "//CountryReceived"));
}

We can specifiy map = dic() within the query method to clear the dictionary out (just before the return statement).

The extra message can be useful when we want several exceptions to be controlled with different messages depending on the context.

So far, we've performed the way to control XML structures and webservices usage, all of that with customized handling exceptions.

Sometimes the namespace in the XML may be different (not only tns), as far as the variable namespace on node treating must be considered. In order to control it, we have to reference it somehow to avoid the fact of writing it over and over.

public string WS_PREFIX { get; set; }

and then we redefine the access to the nodes, adding an sub-inner try-catch to attempt getting the node if it failed.

public String getValue(XPathNavigator xml, String node)
{
    try
    {
        return xml.SelectSingleNode(string.Concat(WS_PREFIX, node), NamespaceManager).Value;
    }
    catch (Exception)
    {
        try
        {
            return xml.SelectSingleNode(node, NamespaceManager).Value;
        }
        catch (Exception)
        {
            // customize handle exception if appropiate
            return string.Concat(XML_NODE_ERROR, SEPARATOR.ToString(), node);    
        }   
    }
}

public void setValue(XPathNavigator xml, string node, string value)
{
    try
    {
        xml.SelectSingleNode(string.Concat(WS_PREFIX, node), NamespaceManager).SetValue(value);
    }
    catch (Exception) 
        {
        try
        {
            xml.SelectSingleNode(node, NamespaceManager).SetValue(value);
        }
        catch (Exception) 
        {
            // customize handle exception if appropiate
        }
    }
}

2. Submitting and handling files

On the other hand, the other recurring themes at Infopath forms are Sharepoint Integration and submitting the information of themselves.

Infopath Framework allows us to submit a form to SharePoint as well as a validation mechanism when doing so. To submit a form we can use several data connections for it, including a default one.

Once the submit data connections are added, the way to use them it´ the next:

FileSubmitConnection sharepointSubmit = (FileSubmitConnection)(DataConnections["DataConnectionString"]);

sharepointSubmit.Filename.SetStringValue("NameOfTheFile");
sharepointSubmit.FolderUrl = "PathOfTheFile";

sharepointSubmit.Execute();

The index could be wrong introduced or even there not be in the data connections array, and the name or the path could be wrong or even unable to access, that means controlling exceptions. Each time we use a data connection we have to do the same, so we should create a method for convenient.

We also can override it if we had a submit default connection.

public Boolean SubmitFile(String path, String file)
{
    return SubmitFile("DefaultSubmit", path, file);
}
public Boolean SubmitFile(String submitConnection, String path, String file)
{
    FileSubmitConnection sharepointSubmit = (FileSubmitConnection)(DataConnections[submitConnection]);
    sharepointSubmit.Filename.SetStringValue(file); 
    sharepointSubmit.FolderUrl = path;
            
    try            
    {
        sharepointSubmit.Execute();
        return true;
    }
    catch (System.Runtime.InteropServices.COMException ex)
    {
        // Handle the exception
    }
    catch (System.Exception ex)
    {
        // Handle the exception
    }
    return false;
}

Analogously, another common task might be converting some forms into a PDF/XPS format.

Infopath Framework provides the method Export through Microsoft.Office.Interop.Infopath which binds a template with an xml data to create a different kind of files from the form. We’re just giving a Template’s Uri and path for the PDF/XPS target file. So, we can export it into a SharePoint’s Location.

Bonded thereto, SubmitFile can also be useful to run as a backup file for the exported file. Hence we create another method to do that action.

An extra method for PDF is added, due to it’s a more common format.

public Boolean ExportPDF(string path, string file)
{
     return ExportWithFormat(path, file, "PDF");
} 
public Boolean ExportWithFormat(string path, string fileWithoutExtension, string externalFormat)
{
    try
    {
        Microsoft.Office.Interop.InfoPath.Application InfApp =
                    new Microsoft.Office.Interop.InfoPath.Application();
        Microsoft.Office.Interop.InfoPath.XDocument InfXDoc = null;
        InfXDoc = InfApp.XDocuments.Open(Uri,
            (int)Microsoft.Office.Interop.InfoPath.XdDocumentVersionMode.xdCanOpenInReadOnlyMode);
        InfXDoc.View.Export(path, string.Concat(File, "." externalFormat), externalFormat);
        return true;
    }
    catch (System.Runtime.InteropServices.COMException ex)
    {
        //Handle
    }
}

public Boolean ExportAndBackup(string path, string file, string format)
{
     // If we succeed exporting the file we upload the xml backup
     if (ExportWithFormat(path, file, "PDF")) SubmitFile(path, file);
}

3. Abbreviating XML Signings

Ultimately, there are usually some forms which have to be signed.

In order to getting such task done, as I mentioned, Infopath containing a mechanism to validate the fields of the forms looking at their types and content fields before to be signed. You cannot sign a invalidate form. Once form validation is asserted, the Infopath form can be signed with a certain certificates.

Infopath contain the options to allow the controls let the user sign the form. In the programmatic side, a sign event is provided, which warn to the program flow when a sign has been added or removed. This is not fully useful without a little extra work, because we have to separate on our own the act of adding or removing signings to perform different actions.

To know if a signing has been added, within the OnSign event, we have to save the signings number before signing, and then compare it with the current signings number, accessible via the references of the argument events.

With the next code we can handle the situation when one signing is properly added.

public void FormEvents_Sign(object sender, SignEventArgs e)
{
    if (!ReadOnly)
    {
        Signature sig = e.SignedDataBlock.Signatures.CreateSignature();
        int count = e.SignedDataBlock.Signatures.Count;

        try
        {
            sig.Sign();
        }
        catch (System.InvalidOperationException) 
        {
            // Handle if appropiate
        }
        e.SignatureWizard = false;
        if (e.SignedDataBlock.Signatures.Count > count)
        {
            // One signing is added, if it is allowed to there be more than one 
        }
    }
    else
    {
            // One signing is removed
    }
}

This is not handy at all, so it is convenient getting a function written that just triggers when the signing is made, and not else. Of course this is easy by adding simple one or two functions called in the if-else statements. But, if we are going to create an adequate class joining every point we have treated, it would be good implement the correct functionality, depending on if a form will be signed or not. Although now it's going to be describe the class which implements the signing functionality, later, including all the functions that we did perform, it will be disposed a whole map about the work done, and well classified.

On the one hand we’ll create a class with all the standard functionalities:

public abstract partial class StandardInfopath : Microsoft.Office.InfoPath.XmlFormHostItem

In the other hand we’ll create a class inherits StandardInfopath and implements the new signing functionality.

public abstract partial class SignableInfopath : StandardInfopath 

As we just want to control the fact of signing, then we create the corresponding abstract method that will have to be override in the Infopath code.

public abstract void OnAfterSign(Object sender, SignEventArgs e); 

and it is suitably placed:

public void FormEvents_Sign(object sender, SignEventArgs e)
{
       Signature sig = e.SignedDataBlock.Signatures.CreateSignature();
       int count = e.SignedDataBlock.Signatures.Count; 
       try
       { 
            sig.Sign();
       }
       catch (System.InvalidOperationException) 
       { 
            // Handle 
       } 
       e.SignatureWizard = false;
       if (e.SignedDataBlock.Signatures.Count > count)
       {
            OnAfterSign(sender, e);
       } 
       else
       {
            // Removing signing, for example OnAfterRemoveSign
       }
    } 
}

so, at Infopath forms we just have to create the event-method OnAfterSign() though the event handler for the event FormEvents_Sign will be also included within InternalStartup when the button to generate the event is pressed.

4. Integrating and using the offered classes

Designer will automatically create a background code file called Form.Designer.cs which includes a partial class that implements the Microsoft.Office.InfoPath.XmlFormHostItem Interface, as we already did the same, we could delete the content of this file and consequently remove the operator “partial” of our classes, having these declarations:

public abstract class StandardInfopath : Microsoft.Office.InfoPath.XmlFormHostItem;
public abstract class SignableInfopath: StandardInfopath;

instead of:

public abstract partial class StandardInfopath : Microsoft.Office.InfoPath.XmlFormHostItem;
public abstract partial class SignableInfopath: StandardInfopath; 

because in that way we had to add a constructor at the partial class in the Form.Designer.cs , this one:

[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public StandardInfopath(System.AddIn.Contract.Collections.IRemoteArgumentArrayContract initArgs) : base(initArgs)
{ 
} 

and add the next:

[global::System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public SignableInfopath(System.AddIn.Contract.Collections.IRemoteArgumentArrayContract initArgs) : base(initArgs)
{ 
} 

for signables.

You can download the Zip file includes both StandardInfopath and SignableInfopath classes.

Thanks for reading.

If you want to contact me, please send an email to juan.carlos.recio.abad@gmail.com.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Juan Carlos Recio Abad
Software Developer
Spain Spain
I´m a Computer Engineer from Spain with a lot of interest and passion about the world of technologies, I love all kind of them, as well as I do it for the science.
I like to create many sorts of utilities and tools, inventing solutions and methods to get solving the challenges which can appear.
 
I also love math and Physics, its misteries and incredible solutions, so I've always been automatizing algorithms and processes related to them on my own, whether for fun or another affairs.
 
Of course, I enjoy reading history, literature, music, and play sport!

Comments and Discussions

 
QuestionHow can we hide th Infopath.Application PinmemberAvani Vadera8hrs 33mins ago 
BugDownload is missing PinmemberAxlHammer12-Aug-14 6:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140827.1 | Last Updated 25 Nov 2013
Article Copyright 2013 by Juan Carlos Recio Abad
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid