Thursday, April 28, 2005

Submitting xsi:nil="true" values from InfoPath to ASP.NET 1.x web services

In my ongoing quest to build an InfoPath solution based on a WSCF web service implemented with ASP.NET 1.1, I have finally been able to:
  • make non-string data types such as xs:date and xs:decimal truly optional in InfoPath
  • submit data to the web service by modifing the XML data in OnSubmitRequest to circumvent the missing support for nillable in .NET 1.x ASP.NET web services

The first issue is caused by the fact that ASP.NET 1.x web service by design do not support nillable elements in the web service parameters. Check the <wsdl:types> element of any ASMX WSDL, and you will not find any nillable="true" elements even if your data XSD contains such elements. The reason for this is the lack of support for NULL in .NET 1.x value types. ASP.NET 2.0 web services will support nillable="true".

After modifying the underlying XSD schemas of your InfoPath as outlined in my previous post to manually re-introduce the nillable="true" attributes, the fields can be left empty in your form. But you will not be able to sumbit the form to the web service. The SOAP request will fail like this:

The SOAP response indicates that an error occurred:
Server was unable to read request. --> There is an error in XML document (47, 104). --> Input string was not in a correct format.

This is caused by this kind of element in the submitted XML:

<s1:Amount xmlns:xsi="" xsi:nil="true"></s1:Amount>

The web service is not able to deserialize this element into a value type due to the lack of support for nillable. Thus, the XML data must be modified before submitting the data to the web service.

InfoPath allows you to use the OnSubmitRequest event to write your own script for submitting the forms data. This event is only available by using Tools-Submitting Forms and selecting "Custom submit using form code" in the 'Submit to' dropdown. Check the 'Edit Form Code' checkbox and click OK to add an event handler for OnSubmitRequest.

This example shows how to look for all instances of a specific element that is NULL, remove the nil attribute and set a dummy value, and finally submitting the form using code:

function XDocument::OnSubmitRequest(eventObj)
var submitDataSource = "Main submit";

//get a collection of
nodes with xsi:nil="true"
var nodeList = XDocument.DOM.selectNodes("/dfs:myFields/dfs:dataFields//s1:Amount[@xsi:nil='true']");

//iterate the list and
set values acceptable to ASP.NET 1.x web service
for(var i=0; i < nodeList.length; i++)
//get node (element)
var xmlNode = nodeList(i);

//remove nil attribute and
//put zero into the element
(uses code from InfoPath common.js)
// The xsi:nil needs to be removed before we set the value.
// Setting the value will mark the document as dirty.
xmlNode.text = -1; //biz logic will interpret this as NULL

//call the Submit method of the
primary data source to send the data

eventObj.ReturnStatus = true;
XDocument.UI.Alert("Failed while sending the request.\r\n" + ex.number + " - " + ex.description);
eventObj.ReturnStatus = false;

You will now be able to submit the form's XML data to the web service. The data will of course contain dummy values for all elements that are really NULL (blank), and your 'save' business logic must handle this accordingly. In addition, you will need to add the same type of logic to the form's request operation and your 'read' business logic. Otherwise the users will see the dummy values when viewing existing data from your web service

PS! remember to set the script language of the form before adding any code, as it is not trivial to change the language afterwards. This KB article explains how to change the language, note that you will need to delete all existing scripts.


Baardsen said...

Generally speaking if you have a disastrous, non-fixable web service that gives you xsi:nil headache and at the same time you have a .NET 1.1 client, you could save your day using a nullable types library from SourceForge:

It definitely made my pants hot and soaky for some days until I can have my truly interoperable web services.

Anonymous said...

do you have an example of doing the above with a datetime? what type of 'dummy' value would you use for datetime? I cannot seem to set the datetime value through script. thanks.

Kjell-Sverre Jerijærvi said...

I would use a date like 1900-01-01 as the date dummy.

This script sets a dt:datetime element value:

var node = XDocument.DOM.selectSingleNode("/dfs:myFields/dfs:dataFields//s1:FixtureDetails/s1:CpDate");

node.nodeTypedValue = "1900-01-01";

Remember to remove the nil attribute before setting the value.

MSXML nodeTypedValue help: