Developing Solutions with Microsoft InfoPath

 

This article is an excerpt from Developing Solutions with Microsoft InfoPath, from Microsoft Press (ISBN 0-7356-2116-0, copyright Microsoft Press 2004 all rights reserved). A portion of Chapter 7: Web Services is adapted here and describes how to use Web services to receive and submit data to and from your forms.

Patrick Halstead founded Autonomy Systems LLC and led solutions development for the first version of InfoPath. He is a 2004 Microsoft MVP for Microsoft Office System–InfoPath**. Vani Mandava-Teredesai** is lead software design engineer for testing on the InfoPath product team and has worked on InfoPath since its inception. Matthew Blain is director of software development for Serriform, LLC, and has worked at Microsoft as a developer for Internet Explorer and on the team that later developed InfoPath.

No part of this chapter may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.

Chapter 7: Web Services

Contents

Designer Support
Error Handling
Rebinding a Web Service
Setting the Timeout When Calling a Web Service
Unsupported Web Service Types
Using .NET Web References
Cross-Domain Rules for Calling Web Services

A Web service is an interface to a software application running on a server across the network. Microsoft Office InfoPath 2003 can use a Web service as a destination to submit data to, as the source of data for creating a form, and as a data connection to receive supplemental data for use in the UI or business logic of a form. In this chapter, we will cover using Web services to receive and submit data for your forms and then discuss some more advanced techniques for working with Web services that the InfoPath designer cannot support.

InfoPath makes the job of integrating your forms with Web services easy by including support for the core Web service standards: XML, WSDL, UDDI, SOAP, and HTTP. You can connect to a Web service you develop specifically for your InfoPath form, or you can connect to existing Web services within your organization, across organizations, or on the Internet.

The Web service functions as the middle tier in a three-tier system. In Chapter 6, you learned how to connect an InfoPath form to a database. If you move your database, change the schema, or modify the stored procedures on the server, you have to update the InfoPath form template. InfoPath supports automatic updates if you deploy your form template to a Web server, but if you deploy through e-mail you have to get users to upgrade the form, and that can be costly. An alternative to upgrading your form every time your database changes is to create a Web service. The Web service can then insulate the form by converting the data from the fixed Web service schema to the changed database.

Figure 7-1 shows a simple three-tier architecture for using a Web service to access a database.

Accessing a database through a Web service versus a direct database connection

Figure 7-1   Accessing a database through a Web service versus a direct database connection

Using a Web service to access a database on a server has both pros and cons:

Pros

  • Reduced redeployment cost when database changes; make changes in one place
  • Supports reuse and extensibility
  • Supports validation at the Web service interface

Cons

  • More work to design, implement, test, and deploy
  • Doesn't work offline unless you implement caching

Middle-tier logic falls into the following buckets:

  • **Receive data from database   **Database rows are extracted and formatted into XML corresponding to the schema of your InfoPath form.
  • **Submit data to database   **Transforms the XML form into sets of rows (rowsets) in your database tables.
  • **Validate data   **Use business rules to validate input. The BizTalk rules engine is a good example of this for workflow.
  • **Authorize user   **This is a common function to implement in a Web service to control read-write permission to databases and line-of-business applications. Windows Integrated Authentication is used when calling a Web service so you can identify the user.
  • **Read data from other applications   **Web services can be used as interfaces to existing corporate applications, including Active Directory.
  • **Reduce deployment cost   **Simplify deployment by moving code from your form to the Web service. If your users do not have the Microsoft .NET Framework, you can move the code to a Web service and do the processing in the middle tier.
  • **Integrate data across multiple sources   **InfoPath can be used to integrate data across multiple sources, but due to security reasons it is often desirable to grant an InfoPath form domain access and therefore access to only a single Web server. This server can provide a Web service that gathers data from other data sources, including other Web services.

When you implement a Web service, you'll want to consider the following:

  • **Error handling   **If validation fails, will you prevent submission of the form?
  • **Version upgrades   **How will you support updates to the schema used on the client?
  • **Offline data   **Will you cache the data when the user goes offline?
  • **Privacy and security   **Will you encrypt the message to prevent others from seeing the contents? InfoPath supports HTTPS (HTTP over SSL) for secure connections.

A Brief Overview of Web Service Standards

InfoPath supports XML, HTTP, SOAP, WSDL, and UDDI Web service standards. What do these abbreviations and acronyms stand for?

  • XML is Extensible Markup Language, a standard for describing structured data used by InfoPath and many other tools; it is discussed throughout this book.
  • HTTP is HyperText Transfer Protocol, which is used to retrieve data from and send data to a Web server. It is the network protocol that InfoPath supports for connecting to Web services. It is also used to load and save templates and forms. You can learn more about how this applies to InfoPath and SharePoint in Chapter 9.
  • SOAP is Simple Object Access Protocol, an XML-based protocol for sending and receiving data to and from a Web service. InfoPath communicates with Web services using SOAP. SOAP messages are well-formed XML documents. InfoPath sends the SOAP message to the Web service using HTTP.
  • WSDL is Web Services Description Language, an XML format that describes the interface to Web services, including the data formats. InfoPath uses WSDL when creating forms and data sources based on a Web service.

UDDI is Universal Description, Discovery, and Integration, a standard interface to directories of Web services. InfoPath can connect to a UDDI server to discover available Web services. To learn more about SOAP and other Web service standards, visit the Web Services Specifications page at Web Services and Other Distributed Technologies Developer Center.

Designer Support

The InfoPath designer can connect Web services in three ways: as the basis for the main data source when creating a form, as a data connection to receive data, and as a data connection to submit data.

Note   The InfoPath designer supports only document/literal encoding for Web services. Methods for calling other types of Web services are described later in this chapter.

Creating a Form Based on a Web Service

If you have a Web service that returns the data you want to display and edit in a form, you can design a form directly from the Web service. InfoPath displays the parameters to and the results from the Web service as the main data source for the form. To do this, choose New From Data Connection on the Design a Form task pane, and then select Web Service. The three options are:

  • **Receive Data   **This creates a form with a data source based on the input and output parameters of the Web service. You can add nodes to the data source, but you cannot change the part of the data schema derived from the Web service (queryFields and dataFields) except by using the Convert Data Source command on the Tools menu (as described later in this chapter).
  • **Submit Data   **This creates a form with a data source based on the input parameters of the selected Web service method. You can add nodes to the data source, but you cannot change the part of the data schema derived from the Web service.
  • **Receive And Submit Data   **This creates a form with a data source based on the input parameters of the selected Web service method. You can add nodes to the data source, but you cannot change the part of the data schema derived from the Web service. InfoPath also creates a submit adapter and enables submit.

Chapter 2 presented a simple example showing how to create an InfoPath form that receives data from a Web service. That example uses the NWorders Web service written by Roger Jennings, author of Introducing Microsoft Office InfoPath 2003 (Microsoft Learning).

Using a Web Service as a Data Connection to Receive Data

A Web service data connection can be used as a secondary data source. A common use for this is with a Web service available on your server that has data that can be used for user interface elements such as drop-down lists. You can also use a Web service data connection to receive data, much like a main data source. With InfoPath, you can add controls from a secondary data source to the form just as you can add controls from the main data source. You can even use them as parameters to a Submit Data Web service data connection. Adding your Web service method as a secondary data source allows you maintain full control over the schema of the main data source. It also allows you add multiple Web service methods to receive data for your template.

To add a data connection to receive data from a Web service, choose Data Connections from the Tools menu, click Add, select Receive Data, click Next, select Web Service, click Next, and then configure the parameters to your Web service. Note that one of the receive data options in the Data Connection Wizard is SharePoint Library Or List. This is a specialized Web service data adapter covered in Chapter 9.

Note   Web service data connections are covered by domain security rules. For more information about domain security, see the section titled "Cross-Domain Rules for Calling Web Services" later in this chapter. For more information about security in general, see Chapter 8.

Using Buttons to Query from and Submit to a Data Connection

You can call a Web service method without writing any code by using a data connection and a button. All you do is add a Button control to your form and then change the action in the Button Properties dialog box. There are five actions you can take, and the right one depends on the type of data connection and result you want. The actions are Query, Refresh, Submit, Rules, and Custom Code.

Query

The Query action is available if the main data source of your form is based on a Web service or database data connection. It requeries the main data connection. It does not have any effect on secondary data connections.

Refresh

The Refresh action is available if your form has a secondary receive data connection. You can select which data source to requery, or you can select to refresh all secondary data sources at once.

Submit

The Submit action is available if your form has submit enabled. A form can have only one Submit action. If you choose the Submit action, changing the action parameters changes the form's global Submit parameters. If you want multiple buttons, each of which submits to a different data connection, you must use Rules or Custom Code instead. If multiple buttons are all set to submit, each of the buttons will do the same thing.

Rules

If you select the Rules And Custom Code action and then click Rules, you can submit to any data connection. This allows you to have multiple buttons on your form, each one submitting to a different data connection. You can add multiple actions so that a single button can query or submit to one or more data connections. You can also add conditions to the actions to enforce workflow or otherwise control the action.

Custom Code

If you select Rules And Custom Code as the action and then click Edit Form Code, you can have the button call your own method in the form code to modify and call any data connection. If you have both rules and form code, rules run first.

Using UDDI

UDDI is a tool for locating Web services. This is a good way to discover some of the many services available, including bank rate services, weather services, and mapping services. If you do not know the URL of the Web service you wish to use, you can click the Search UDDI button on the Web Service Details page of the Data Connection Wizard. This will open the Search Web Service dialog box, shown in Figure 7-2, where you can search a UDDI server. Once you select a Web service from the Search Web Service dialog box, InfoPath will fill in the location of the Web service in the Data Connection Wizard.

Searching for a Web service using UDDI

Figure 7-2. Searching for a Web service using UDDI

Using a Web Service as a Data Connection to Submit Data

Using a data connection to submit data to a Web service is a powerful technique. The difference between a submit data connection and a receive data connection is that InfoPath provides automatic mapping between the fields used in your form (both the main data source and other receive data connections) and the parameters sent to the Web service. The next few examples will demonstrate the options for mapping values to a Web service, all combined into a single PictureBlog Web service and InfoPath template.

Note   Many of the options for the submit Web service are new in InfoPath SP1. A number of articles online describe ways of achieving this functionality with the original release version of InfoPath, but those techniques are not needed with SP1.

PictureBlog Schema

Mobile road warriors have a plethora of form options to choose from including customer visits, interview feedback, property appraisal, insurance claim, medical diagnosis, and so on. For the submit Web service examples, we'll use a schema for a mobile weblog, or blog. Imagine you're on vacation and you want to upload some of the pictures you are taking to your database at home, but the Hawaiian hotel you are staying at doesn't have a wireless network that extends to the beach where you are sipping your Mai Tai and taking snapshots of Junior's first surfing lesson. So you load the pictures into your InfoPath form with a few notes, dates, and locations, all to be submitted later, when back in range of the wireless network. Here's the schema for the PictureBlog form:

<xs:schema targetNamespace="http://schemas.infopathdev.com/sample/pictureblog"
 xmlns:pb="http://schemas.infopathdev.com/sample/pictureblog"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 elementFormDefault="qualified"
 attributeFormDefault="unqualified">
  <xs:element name="PictureBlog">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Entry" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
              <xs:sequence>"
              <xs:element name="Title" type="xs:string" />
              <xs:element name="Date" type="xs:date"/>
              <xs:element name="Time" type="xs:time"/>
              <xs:element name="Location" type="xs:string" />
              <xs:element name="Image" nillable="true"
                type="xs:base64Binary"/>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Save this file as PictureBlog.xsd or locate it in the companion content for this book. With this schema file, we'll create an InfoPath form with a Submit button to send the entire form to a Web service.

Creating the PictureBlog Submit Web Service

Launch Visual Studio. Create a new Visual Basic ASP.NET Web Service project. Set the location of the project to http://localhost/PictureBlogService. Click the Click Here To Switch To Code View link, and then add the following Imports statement and methods:

Imports System.Xml ' at the top of the file 

' Add one entry and return the entry ID.
<WebMethod()> _
Public Function AddOneEntry(ByVal entryTitle As String, _
    ByVal entryDate As Date, _
    ByVal entryTime As Date, _
    ByVal entryLocation As String, _
    ByVal entryImage As Byte()) As String 
    TraceRequestBody()
    Return AddOneEntryInternal(entryTitle, entryDate, entryTime, _
     entryLocation, entryImage)
End Function 

' Add multiple entries.
<WebMethod()> _
Public Sub AddEntriesAsXml(ByVal entryXml As System.Xml.XmlNode)
    Dim nsmgr As XmlNamespaceManager
    Dim entryNodes As XmlNodeList 

    TraceRequestBody() 

    HttpContext.Current.Trace.Write("Root element in request is " & _
     entryXml.Name)

    nsmgr = New XmlNamespaceManager(entryXml.OwnerDocument.NameTable)
    nsmgr.AddNamespace("pb", _"
      "http://schemas.infopathdev.com/sample/pictureblog")
    ' Use recursive select as input data may have a wrapper node or two.
    entryNodes = entryXml.SelectNodes("//pb:Entry", nsmgr)

    HttpContext.Current.Trace.Write("Adding " &amp; entryNodes.Count & _
     " entries.")

   ' Call the AddOneEntryInternal method for each entry.
    For Each node As XmlNode In entryNodes
        ' Manually break apart each node.
        Dim entryTitle As String
        Dim entryDate As Date
        Dim entryTime As Date
        Dim entryLocation As String
        Dim entryImage As Byte()
        Dim id as String 

        entryTitle = node.SelectSingleNode"pb:Title", nsmgr).InnerText
        entryDate = Date.Parse(node.SelectSingleNode( _
         "pb:Date", nsmgr).InnerText)
        entryTime = Date.Parse(node.SelectSingleNode( _
         "pb:Time", nsmgr).InnerText)
        entryLocation = node.SelectSingleNode("pb:Location", nsmgr).InnerText
        entryImage = Convert.FromBase64String( _
         node.SelectSingleNode("pb:Image", nsmgr).InnerText) 

        id = AddOneEntryInternal(entryTitle, entryDate, entryTime, _
         entryLocation, entryImage)
        HttpContext.Current.Trace.Write("Entry ID", id)
    Next
End Sub

' Add multiple entries.
<WebMethod()> _
Public Sub AddEntriesAsXmlString(ByVal entryXmlString As String)
    Dim xmlDoc As XmlDocument 

    ' Do processing such as signature validation here. 

    xmlDoc = New XmlDocument
    xmlDoc.LoadXml(entryXmlString)
    AddEntriesAsXml(xmlDoc.DocumentElement)
End Sub 

' Write HTTP Request body to trace output for debugging.
Private Sub TraceRequestBody()
    HttpContext.Current.Request.InputStream.Seek(0, IO.SeekOrigin.Begin)
    Dim sr As System.IO.StreamReader
    sr = New System.IO.StreamReader(HttpContext.Current.Request.InputStream)
    HttpContext.Current.Trace.Write("Request Body", sr.ReadToEnd())
End Sub 

' Fake entry processing: just write values to trace log.
Private Function AddOneEntryInternal(ByVal entryTitle As String, _
    ByVal entryDate As Date, _
    ByVal entryTime As Date, _
    ByVal entryLocation As String, _
    ByVal entryImage As Byte()) 

    Dim traceContext As System.Web.TraceContext
    traceContext = HttpContext.Current.Trace
    traceContext.Write("Title: " &amp; entryTitle)
    traceContext.Write("Date: " &amp; entryDate)
    traceContext.Write("Time: " &amp; entryTime.TimeOfDay.ToString())
    traceContext.Write("Location: " &amp; entryLocation)
    If (entryImage Is Nothing) Then
        traceContext.Write("Image size (bytes): no image")
    Else
        traceContext.Write("Image size (bytes): " &amp; entryImage.Length)
    End If
 
   ' return fake entry ID
   Return "1234"
End Function

Press F5 to build the Web service and see the URL and documentation.

Tip   To install ASP.NET, install the .NET Framework. In certain situations, ASP.NET is not fully installed. If you encounter unexpected problems with an ASP.NET Web service, you can quickly reinstall ASP.NET by running %WINDIR%\Microsoft.NET\Framework\v1.1.4322\ aspnet_regiis.exe –i in a command prompt.

You now have a Web service with three methods: AddOneEntry, which takes all the parameters to create one entry; AddEntriesAsXml, which takes any XML node, searches for entries in the XML, and then adds each one it finds; and AddEntriesAsXmlString, which takes a string, converts it to an XML node, and calls AddEntriesAsXml. All of these methods call AddEntryInternal, which uses the ASP.NET trace mechanism to write some debugging information. In a real application, AddEntryInternal would do something useful with the data.

The Web service can do any middle-tier task with the data. This can include verifying permissions with Windows Integrated Authentication, running calculations on the data, submitting data to multiple databases, submitting the data to SharePoint using WebDAV or the SharePoint server-side class library, logging events, and sending e-mail.

Creating the PictureBlog Form

Now you will build the InfoPath form to connect to the Web service.

Design a new form, choosing New From XML Document Or Schema. Use the PictureBlog.xsd file that we created earlier. In the Data Source task pane, and drag and drop the Entry node into the view, and then choose Repeating Section with Controls from the shortcut menu that appears.

You now have a basic form with the essential elements of the schema. The next step is to connect it to the Web service.

Using the Parameterized Function as a Submit Adapter

  1. Choose Data Connections from the Tools menu, and then click Add.

  2. In the Data Connection Wizard, select Submit Data, and then click Next.

  3. Select To A Web Service, and then click Next.

  4. Enter the URL to your new Web service. If you use the name suggested earlier, the URL will be http://localhost/PictureBlogService/Service1.asmx.

  5. Click Next. A list of methods (operations) in the Web service is displayed.

  6. Select AddOneEntry, and then click Next. The parameters for the selected method are displayed. Each parameter must be mapped.

  7. Select the entryTitle parameter, click the Modify icon to the right of the Field Or Group text box, expand the main data source, select Title, and then click OK.

  8. Repeat the process for each parameter, mapping entryTitle to Title, entryDate to Date, entryTime to Time, entryLocation to Location, and entryImage to Image. Leave the Include drop-down list set to Text and Child Elements Only for each element. The dialog box will look like that shown in Figure 7-3.

    Mapping the data for parameters of a submit Web service data adapter

    Figure 7-3. Mapping the data for parameters of a submit Web service data adapter

  9. Click Next to complete the mapping.

  10. Change the name of the data connection to AddOneEntry, click Finish, and then click Close.

  11. From the Controls task pane, drag and drop a button into the view below the repeating table.

  12. Right-click on the button, and then choose Button Properties.

  13. In the Button Properties dialog box, change the label to AddOneEntry.

  14. Click Rules, click Add, and then click Add Action.

  15. In the Action drop-down list, select Submit Using a Data Connection.

  16. In the Data Connection drop-down list, ensure that AddOneEntry is selected, as shown in Figure 7-4.

  17. Click OK four times to close the open dialog boxes.

    Setting the action of a button to submit to the AddOneEntry data connection

    Figure 7-4. Setting the action of a button to submit to the AddOneEntry data connection

You have connected the PictureBlog form to the PictureBlog Web service. Test the data connection by previewing the form, entering data for one entry, and then clicking AddOneEntry. The submission should succeed silently. You can add more rules to the button or use code to add a dialog box message on successful submit.

To view what happened on your Web service, go to trace.axd in the application folder of your Web service (for example, http://localhost/PictureBlogService/trace.axd). Click the View Details link on the most recent trace entry, and then read the trace information. It should contain the title and other values you entered when you submitted the form, as shown in Figure 7-5.

Important   The PictureBlog Web service uses the trace mechanism in ASP.NET. To enable tracing, edit the web.config file in the same folder as your Web service (for example, C:\Inetpub\wwwroot\ PictureBlogService\Web.config) and then modify it so that there is a configuration/system.web/trace node with the enabled attribute set to true. The default web.config created by Visual Studio will have this node with the enabled attribute set to false; change it to true.

Once you have enabled tracing, you must resubmit your form to create a trace entry. To view the trace results, go to trace.axd in the same folder as your Web service.

Viewing the trace results after calling the AddOneEntry Web service method

Figure 7-5. Viewing the trace results after calling the AddOneEntry Web service method

Here are the advantages of using a parameterized Web service method:

  • Correct type information is propagated across the network.
  • Types are deserialized automatically. (Note that the date and binary values are of the correct types.)
  • The Web service specifies a strong type relationship for use in multiple clients.
  • Loose coupling of InfoPath template schema and Web service.

Here are the disadvantages of using a parameterized Web service method:

  • It is time consuming to set up a large mapping relationship.
  • You do not have full control over the mapping: the connection we created in this section will fail if you try to submit more than one entry.

Note   Serialization is the process of converting a data structure into a "serial" form such as a stream or file of XML. The stream of data can then be saved to disk or transferred across a network. Serialization converts the object into a stream of data, and deserialization converts the stream of data back into an object.

Using the XML-Based Function as a Submit Adapter

Using the XML-based function as a submit adapter is similar to the previous procedure:

  1. Open the Data Connections dialog box, and then add a new Submit Data To A Web Service data connection.
  2. Enter the URL of your Web service, and then click Next.
  3. Select the AddEntriesAsXml method, and then click Next.
  4. For the entryXml parameter, select the PictureBlog node, select XML Subtree, Including Selected Element in the Include drop-down list, and then click Next.
  5. Name the adapter AddEntriesAsXml, click Finish, and then click Close.
  6. Add a new button to the view.
  7. Change the button label to AddEntriesAsXml.
  8. Click Rules, click Add, click Add Action, select Submit Using a Data Connection, and then select the AddEntriesAsXml data connection.
  9. Click OK four times to close all open dialog boxes.

You now have a data connection that connects to the XML node–based Web service. Test the new data connection by previewing the form, creating an entry, clicking AddEntriesAsXML, and then looking at the results of the ASP.NET trace.

In this example, you selected the root node of the main data source, PictureBlog, to submit. You also changed the Include option to XML Subtree, Including Selected Element. This tells InfoPath to send the entire PictureBlog node, including the PictureBlog tag itself. You can also select to submit the entire form by selecting the Entire Form option. This wraps the entire XML of the form in an IPDocument node in the http://schemas.microsoft.com/office/infopath/2003/dataFormSolution namespace, including the processing instructions. This sample Web service can handle both forms by doing a recursive search on the submitted XML to look for the desired data. You can write a Web service to explicitly handle one form or the other.

Here are the advantages of using an XML node Web service method:

  • Mapping within InfoPath is straightforward.
  • A single Web service method can handle multiple XML schemas.

Here are the disadvantages of using an XML node Web service method:

  • Strong typing is not enforced.
  • Parsing of data must be done in code in the Web service.
  • Tight coupling of InfoPath template schema and Web service: if one changes, the other must change too.

One way to combine the best aspects of XML node Web service methods and parameterized Web methods is to use a typed XML node. This is described later in the chapter.

Using the String-Wrapped XML-Based Function as a Submit Adapter

Using the string-wrapped XML-based function as a submit adapter is similar to the previous procedure:

  1. Open the Data Connections dialog box, and then add a new Submit Data To a Web Service data connection.
  2. Enter the URL of your Web service, and then click Next.
  3. Select the AddEntriesAsXmlString method, and then click Next.
  4. For the entryXml parameter, select Entire Form, select the Submit Data As A String check box, and then click Next.
  5. Name the adapter AddEntriesAsXmlString, click Finish, and then click Close.
  6. Add a new button to the view.
  7. Change the button label to AddEntriesAsXmlString.
  8. Click Rules, click Add, Click Add Action, select Submit Using A Data Connection, and then select the AddEntriesAsXml data connection.
  9. Click OK four times to close all open dialog boxes.

You now have a data connection that connects to the XML node–based Web service. Test the new data connection by previewing the form, creating an entry, clicking AddEntriesAsXmlString, and then looking at the results of the ASP.NET trace.

The sample Web service parses the string as XML and then calls the AddEntriesAsXml method. If you want to add additional code that relies on the string, such as code that verifies a digital signature, you can do so with the submitted string.

XML Data Versus Strings

If you are going to pass XML data to your Web service, does it make sense to have the Web service accept it as a string? In general, the answer is no, for the following reasons:

  • The Web service must validate the contents of the string to prevent clients from passing bogus XML, such as, "Hello, World!" and other poorly formed XML strings.
  • Passing strings around requires extra steps to load them into XML documents to take advantage of the XML structure.

InfoPath cannot parse Web services that return strings. The crux of the problem is that string data is weakly typed. If your Web service is just a gatekeeper responsible for passing data through to a workflow process, weakly typed data such as strings might be sufficient, but for Web services that will do real work on the data, you'll want to send strongly typed XML data.

Using a Typed, Structured Web Service Method

Another way to work with a Web service is to pass typed XML data across the network. One pattern is to use the same schema in your InfoPath template as in the Web service and pass the entire document in either direction. Let's add this functionality to the PictureBlog Web service. Visual Studio .NET ships with an SDK that includes an xsd.exe tool that creates a class definition based on an XML schema. We will use this tool to create a class that can serialize and deserialize to the same schema the form uses.

Adding a Typed, Structured Web Service Method

  1. Launch a Visual Studio .NET command prompt.

  2. Change to the directory containing the PictureBlog.xsd file.

  3. Run the command xsd.exe /c /language:VB PictureBlog.xsd. This generates a file named PictureBlog.vb containing the class definition corresponding to the PictureBlog schema.

  4. Add the PictureBlog.vb file to your PictureBlogService Visual Basic project, by choosing Add Existing Item from the Project menu, locating and selecting the PictureBlog.vb file, and then clicking Open.

  5. Add the following method to the service1.asmx.vb file just above the End Class statement:

    <WebMethod()> _  Sub AddEntriesAsPictureBlog(ByVal entries As PictureBlog)
        TraceRequestBody()
    
        HttpContext.Current.Trace.Write("Adding " & entries.Entry.Length & _
            " entries.") 
    
        ' Call the AddOneEntryInternal method for each entry.
        For Each entry As PictureBlogEntry In entries.Entry
            AddOneEntryInternal(entry.Title, entry.Date, entry.Time, _
                entry.Location, entry.Image)
            HttpContext.Current.Trace.Write("---")
        Next
    End Sub
    
  6. Save and recompile your Web service. You now have a new AddEntriesAsPictureBlog method that takes typed XML.

  7. Design the PictureBlog form.

  8. Open the Data Connections dialog box, and then add a new Submit To A Web Service data connection.

  9. Enter the URL of your Web service, and then click Next.

  10. Select the AddEntriesAsPictureBlog method, and then click Next.

  11. For the entries parameter, select the PictureBlog node, leave the Include drop-down list set to Text And Child Elements Only, and then click Next.

    Caution   It might seem counterintuitive to include text and child elements only and not select XML Subtree, Including The Selected Element. However, the Web service definition for the AddEntriesAsPictureBlog operation includes the definition of the root node itself. If you include the selected element, InfoPath will wrap the node twice and the deserialization will fail.

  12. Name the adapter AddEntriesAsPictureBlog, click Finish, and then click Close.

  13. Add a new button to the view.

  14. Change the button label to AddEntriesAsPictureBlog.

  15. Click Rules, click Add, click Add Action, select the Submit Using A Data Connection, and then select the AddEntriesAsPictureBlog data connection.

  16. Click OK four times to close all open dialog boxes.

Now you have created a very powerful combination because the form and Web service speak the same "language." The main disadvantage here is the tight coupling between schemas. If the schema of the template changes, you must either change the Web service or write an adapter layer in the InfoPath template or the Web service.

ADO.NET Dataset Integration

InfoPath supports ADO.NET datasets transmitted over a Web service, including support for DiffGrams. (A DiffGram is an XML format that describes an ADO.NET dataset, including updates, insertions, and deletions made to the dataset.) To use this, your Web service should implement a method that returns a dataset, and a method that takes a dataset as an input parameter. You then use those methods for a receive data connection and a submit data connection. The simplest form of these methods is shown here. These methods are in a class with an instance of a data adapter called MyDataAdapter1 and in a project with a generated dataset class of type MyDataSet. The full source code for this example is included as part of the companion content for this book:

<WebMethod()> _
Public Function GetDataSet() As MyDataSet
    Dim ds As MyDataSet
    ds = New MyDataSet
    myDataAdapter1.Fill(ds)
    Return ds
End Function 

<WebMethod()> _
Public Sub UpdateDataSet(ByVal ds As MyDataSet)
    myDataAdapter1.Update(ds)
End Sub

You can use these methods much like any other Web service method. For example, you can create a form to receive and submit data to this Web service. To connect the submit adapter to the Web service, select the group myFields/dataFields/s0:GetDataSetResponse/GetDataSetResult/ns1:DataSet1. The default options of including the Text And Child Elements Only is correct.

InfoPath uses the schema information in the dataset to manage relationships, constraints, read-only columns, and auto-increment columns.

You can also create a form based on a submit data connection without a corresponding receive. If the Web service method takes a typed dataset as a parameter, InfoPath creates a template that lets you add new items to the dataset read by the Web service method.

InfoPath does not support displaying more than one dataset at a time. It also does not support DeleteRule and UpdateRule properties, and copying and pasting nested datasets may generate unexpected relationships.

Using Web Service Data Connections in Code

You can extend the functionality beyond what the InfoPath designer supports for Web services by using code. For example, you can submit to a Web service by setting the parameters in code instead of letting InfoPath map the values. To do this, you create a receive data adapter, update the query parameters by using code, and then call Query. Here is an example of using this to call the PictureBlog Web service used earlier in this chapter.

To Call the PictureBlog Web Service from Code in InfoPath

  1. Launch Visual Studio. Create a new Visual Basic InfoPath Form Template project named AddPictureBlogEntry.

  2. Choose Open InfoPath from the Project menu.

  3. In InfoPath, choose Data Connections from the Tools menu.

  4. Click Add, select Receive Data, and then click Next.

  5. Select Web Service, and then click Next.

  6. Enter the URL to the PictureBlog Web service (for example, http://localhost/PictureBlogService/Service1.asmx), and then click Next.

  7. Select the AddOneEntry operation, and then click Next.

  8. Click Next, leaving the parameters values blank.

  9. Deselect the Automatically Retrieve Data When Form Is Opened check box, change the name for the data connection to AddOneEntryReceive, click Finish, and then click Close.

  10. Switch to Visual Studio. A dialog box appears asking if you want to automatically update expressions. Click Yes.

  11. Switch to InfoPath.

  12. In the Controls task pane, drag and drop a Picture control in the view. When the Insert Picture Control dialog box appears, select Included In The Form and then click OK.

  13. Insert a button in the view.

  14. Right-click on the button, and then choose Button Properties.

  15. Change the button label to Create Entry, and then click Edit Form Code.

  16. Add the following method to form code just above the End Class statement:

    ' Create a PictureBlog entry with a dummy title, current date and 
    ' time, and picture data from the form. 
    ' Returns the ID of the created entry.
    Private Function AddEntry() As String
        Dim dataObj As DataObject
        Dim dataDom As IXMLDOMDocument2
        Dim nodeImage As IXMLDOMNode
        Dim nodeEntry As IXMLDOMNode
        Dim nodeResult As IXMLDOMNode
        Dim dateString As String 
    
        ' Get current date time. Format s = 2000-08-17T23:32:32.
        dateString = DateTime.Now.ToString("s")
        ' Get image from user's entry.
        nodeImage = thisXDocument.DOM.selectSingleNode("//my:field1") 
    
        dataObj = thisXDocument.DataObjects("AddOneEntryReceive")
        ' Cast the DOM to a DOMDocument2 to access setProperty.
        dataDom = dataObj.DOM
        dataDom.setProperty("SelectionLanguage", "XPath")
        dataDom.setProperty("SelectionNamespaces", _
        "xmlns:dfs=""" & _
        "http://schemas.microsoft.com/office/infopath/2003/dataFormSolution" & _
         """ xmlns:s0=""http://tempuri.org/PictureBlogService/Service1""")
    
        ' Select the query parameters of the Web service.
        nodeEntry = dataObj.DOM.selectSingleNode( _
         "/dfs:myFields/dfs:queryFields/s0:AddOneEntry")
        ' Assign values to each relevant parameter.
        nodeEntry.selectSingleNode("s0:entryTitle").text = "Autogenerated entry"
        nodeEntry.selectSingleNode("s0:entryDate").text = dateString.Split("T")(0)
        nodeEntry.selectSingleNode("s0:entryTime").text = dateString.Split("T")(1)
        nodeEntry.selectSingleNode("s0:entryImage").text = nodeImage.text
    
        ' Call the Web service method.
        dataObj.Query()
    
        ' Get the result from the Web service.
        nodeResult = dataObj.DOM.selectSingleNode( _
         "/dfs:myFields/dfs:dataFields/s0:AddOneEntryResponse/" & _
         "s0:AddOneEntryResult")
    
        Return nodeResult.text
    End Function
    
  17. If your Web service uses a namespace other than http://tempuri.org/PictureBlogService/Service1, be sure to update the definition of the s0 namespace prefix in the above method. The namespace used here is what Visual Studio and ASP.NET autogenerate if your project is called PictureBlogService and you use the autogenerated service1.asmx file. You can see the namespace required by right-clicking on the node in the Data Source task pane in InfoPath.

  18. Add the following code in the button event handler to call the above method:

    Dim entryId As String
    entryId = AddEntry()
    thisXDocument.UI.Alert("Created entry " & entryId)
    
  19. Save the form code, and press F5 to preview the form.

  20. Add a picture, and then click the Create Entry button. You should get a message with the entry ID created, and you can view the trace information from the Web service to verify that you called it correctly.

This example demonstrates how to access the parameters and return values of a Web service adapter through code. Note that you need to set the selection namespace property on the data adapter DOM to correctly select the node. In addition to setting the text values of nodes, you can insert entire XML nodes into the query parameters. You can display the return values from this call directly in your view by binding controls to the dataFields nodes in the data source for this data connection.

The Return Value of a Web Service Submit Data Connection

There are two types of Web service data connections: receive and submit. At the beginning of this chapter, you learned how to create a submit data connection through the InfoPath designer UI, letting InfoPath map the values from your primary data source into the parameters of the Web service method. In the previous section, you learned how to use code and a receive data connection to submit and receive data. You can also use code to read the value returned by a submit data connection. The OutputLocation property of the WebServiceAdapter object contains the XML returned by the Web service wrapped in the same way InfoPath wraps the return value of a receive Web service data connection.

The following function, added to the PictureBlog form, submits using the AddOneEntry data connection and returns the ID of the entry created:

Public Function AddOneEntryWithSubmitAdapter() As String
    Dim dataObj As WebServiceAdapter2
    Dim domReturn As IXMLDOMDocument2
    Dim nodeReturn As IXMLDOMNode
    Dim nodeResult As IXMLDOMNode

    dataObj = thisXDocument.DataAdapters("AddOneEntry")
    dataObj.Submit()
    nodeReturn = dataObj.OutputLocation

    ' Set namespace properties on return DOM so we can select result.
    domReturn = nodeReturn.ownerDocument
    domReturn.setProperty("SelectionLanguage", "XPath")
    domReturn.setProperty("SelectionNamespaces", _
     "xmlns:dfs=""" & _
     "http://schemas.microsoft.com/office/infopath/2003/dataFormSolution" & _
     """ xmlns:s0=""http://tempuri.org/PictureBlogService/Service1""")

    ' Get the result from the Web service.
    nodeResult = nodeReturn.selectSingleNode( _
     "/dfs:dataFields/s0:AddOneEntryResponse/" & _
     "s0:AddOneEntryResult")

    Return nodeResult.text
End Function

You will notice a few differences between the AddOneEntryWithSubmitAdapter method and the AddEntry method of the AddPictureBlogEntry form template:

  • AddOneEntryWithSubmitAdapter uses the XDocument.DataAdapters connection instead of the DataObjects connection.
  • Because AddOneEntryWithSubmitAdapter uses a submit connection, the submit parameters are set automatically by InfoPath based on the mapping established when the data connection was created.
  • AddOneEntryWithSubmitAdapter gets the result from the OutputLocation property of the data adapter.
  • The XPath to the actual result node starts at the dfs:dataFields node because the output is not wrapped in the dfs:myFields node, as in a submit data connection.

Error Handling

If you want to trap the errors returned by InfoPath and handle them yourself, call submit using code instead of using rules, and then wrap the submit call in a Try…Catch block. In code, using the .NET Framework, the exception is a System.Runtime.InteropServices.COMException with an error code of 0x80043032. If you are using script, the exception will be numbered 0x80043032, or -2147209166 decimal. You can parse the exception message to get more information. You can also parse the XML SOAP response by looking at the ErrorsLocation property of the DataAdapter object.

Note   The ErrorsLocation and OutputLocation properties of a DataAdapter are not cleared when Query is called; they are set only when values are returned.

Another technique to get more detailed exception information is to use .NET Web references in place of the InfoPath Web service adapter. If you do that, you can trap the .NET exceptions instead of the InfoPath wrapped exceptions.

Rebinding a Web Service

Two common events require you to change a Web service data connection. One is a change in the interface to the service interface, and other is a change to the service URL. The interface to a service might change during development, and updating the form to match the new interface should be considered a development task. Once a service is finished, the interface should not change.

You might work with a Web service that has multiple potential endpoints. For example, you might have a test Web service and a production Web service that expose the same interface and functionality but have different URLs and different underlying data. In this section, you will learn how to update your form at design time to a different interface, how to change to a different URL for the same interface by editing the manifest.xsf file, and how to change to a different URL for the same interface at run time by using code.

Updating Data Connection Web Service Binding in the Designer

You can update any data source through the Data Connections Wizard. This refreshes the connection, which can be used in a number of scenarios: changing the entire Web service being used to a different one, refreshing the description of a Web service under development, and changing the endpoint of the Web service.

To do this, edit your form template, and then choose Data Connections from the Tools menu. Select the data connection you want to update. If you created your form to receive data from a Web service, the data connection will be called "Main query" by default. If you created your form to send data to a Web service, the corresponding data connection will be called "Main submit" by default. Go through the rest of the Data Connection Wizard, editing parameters such as the URL as necessary.

If the interface of the Web service you are connecting to has changed parameters, you might need to update the data bindings. InfoPath will try to update the binding for you, but it cannot map all changes. You must verify both the binding of controls in each view and the binding of the submit data connection. To rebind the submit data connection, go through the Data Connection Wizard for the submit connection and verify all of the parameters.

Tip   If you are changing both the submit data connection and the receive data connection, update the receive data connection first. Otherwise, you will need to update the submit data connection twice in case the receive data connection parameters changed.

You can also use the Convert Main Data Source feature (choose Convert Main Data Source from the Tools menu) to do the same thing, but only with the main data source.

Updating Data Connection Web Service URL Binding in the Manifest

If the only aspect of the Web service that has changed is the URL, you can change it in the manifest. Extract the form files, and edit the manifest.xsf file in a text editor. For every Web service adapter in your form, you will find an xsf:webServiceAdapter node. Here is the definition of a Web service adapter that submits to the AddEntriesAsXml method of the PictureBlog Web service running on the local computer:

<xsf:webServiceAdapter wsdlUrl=http://localhost/PictureBlogService/Service1.asmx?WSDL
queryAllowed="no" submitAllowed="yes" name="AddEntriesAsXml">
    <xsf:operation name="AddEntriesAsXml" soapAction="http://tempuri.org/PictureBlogService/
       Service1/AddEntriesAsXml" serviceUrl="http://localhost/PictureBlogService/
       Service1.asmx">
       <xsf:input source="Submit.xml">
            <xsf:partFragment match="/dfs:myFields/dfs:dataFields/ns2:AddEntriesAsXml/
                ns2:entryXml" replaceWith="/ns1:PictureBlog" filter="."></xsf:partFragment>
        </xsf:input>
   </xsf:operation>
</xsf:webServiceAdapter>

To change the URL, change the serviceUrl attribute of the xsf:operation node.

Note   The xsf:webServiceAdapter node can be found in one of four places in the manifest.xsf file. The main query adapter is inside the xsf:query node. The main submit adapter is inside the xsf:submit node. Secondary data sources (retrieve data connections) are inside the xsf:dataObjects node. Secondary data sources (submit data connections) are inside the xsf:dataAdapters node.

Because the example uses http://localhost, it attempts to connect to the machine where the form is opened, so it probably won't work for anyone except the original developer. The URL must be changed to the absolute URL of the Web service.

Changing the Submit Parameter Mapping in the Manifest

In addition to the serviceUrl attribute in the manifest.xsf file, you can change the submit parameter mapping. This is particularly useful if you want to change something that cannot be changed in the Data Connection Wizard. For example, in the AddOneEntry adapter in the -PictureBlog form template, the Title parameter is mapped like this:

<xsf:partFragment
 match="/dfs:myFields/dfs:dataFields/ns2:AddOneEntry/ns2:entryTitle"
 replaceWith="/ns1:PictureBlog/ns1:Entry/ns1:Title">
</xsf:partFragment>

This selects the title of the first entry in the form if there are multiple entries. If you want to change it to the title of the last entry, you can change the XPath of the replaceWith attribute so the selected Entry is the last one using position()=last(). The resulting xsf:partFragment node will look like this:

<xsf:partFragment
 match="/dfs:myFields/dfs:dataFields/ns2:AddOneEntry/ns2:entryTitle"
 replaceWith="/ns1:PictureBlog/ns1:Entry[position()=last()]/ns1:Title">
</xsf:partFragment>

Don't forget to change all the other parameters of the Web service to match. Other properties of the data connection, such as soapAction, can be changed directly in the manifest.xsf file. However, using the Data Connection Wizard, where possible, is the safest way to change these parameters.

Updating Data Connection Web Service URL Binding with Code

You might have a form template that is deployed to multiple locations, such as a test server and a production server. If you want to avoid making changes in the designer at deployment time, you can modify the Web service URL in code. The properties of the xsf:operation node are available at run time. The following method can be called from the OnLoad event handler to rebind the Web service to a URL on the same server as the publish URL:

' Change the service URL for the adapter specified by adapterName
' to the Solution URL + the specified relative URL
Private Sub RebindSubmitAdapter(ByVal adapterName As String, _
                                ByVal webserviceRelativeUrl As String)
    Dim solutionUrl As Uri
    Dim webserviceUrl As Uri
    Dim adapter As WebServiceAdapter2
    Dim xmlOperation As IXMLDOMDocument2
    Dim nodeServiceUrl As IXMLDOMNode 

    ' calculate the new URL
    solutionUrl = New Uri(thisXDocument.Solution.URI)
    webserviceUrl = New Uri(solutionUrl, webserviceRelativeUrl) 

    ' get the data adapter
    adapter = thisXDocument.DataAdapters(adapterName) 
    ' get the operation from the data adapter
    ' this is the manifest's xsf:operation node minus child nodes.
    xmlOperation = thisXDocument.CreateDOM()
    xmlOperation.validateOnParse = False
    xmlOperation.loadXML(adapter.Operation) 

    ' change the service URL to the new URL
    nodeServiceUrl = _ 
     xmlOperation.documentElement.selectSingleNode("@serviceUrl")
    nodeServiceUrl.text = webserviceUrl.AbsoluteUri 
    ' save the operation back to the data adapter
    adapter.Operation = xmlOperation.xml
End Sub

In your OnLoad event handler, call the method like this: RebindSubmitAdapter("Submit", "/path/to/webservice.asmx"). You can also pass in an absolute URL that you have calculated with some other mechanism as the second parameter to the RebindSubmitAdapter method.

Setting the Timeout When Calling a Web Service

Web services are calls to remote machines. Network speed is variable, and the processing time for a complex Web service might also be long. By default, the InfoPath Web service adapter times out after 30 seconds. You can change this value in your code. Call the following method in your OnLoad event handler or just before you query from or submit to a Web service:

Public Sub SetWSAdapterTimeout(ByVal adapterName As String, _
         ByVal seconds As Integer)
    Dim dataObj As DataObject
    Dim wsAdapter As WebServiceAdapter2 

    dataObj = thisXDocument.DataObjects(adapterName)
    wsAdapter = dataObj.QueryAdapter 

    wsAdapter.Timeout = seconds
End Sub

If your data connection is set to retrieve data every time the form is opened, you will not have a chance to set the timeout before the Web service is called. To fix this, first deselect the Automatically Retrieve Data When Form Is Opened check box in the Data Connection Wizard, then, in your OnLoad event handler, set the timeout value and call Query on the Web service adapter.

Unsupported Web Service Types

InfoPath can send and receive data to and from a wide variety of Web services, but not all types of Web services are supported. This section will discuss some aspects of Web services that are not directly supported in the designer and how to work around those issues.

RPC/Encoded Web Services

The SOAP protocol supports a number of binding styles. The built-in InfoPath Web Service Adapter supports only the document/literal style. If your Web service uses RPC/encoded or another style of binding, you have three options:

  • If you have full control over the Web service, you can generate a new binding using the tool you used to create your Web service.
  • If you have access to a server, you can create a proxy Web service. The proxy wraps the RPC/encoded Web service and provides a document/literal Web service that the InfoPath designer can use. For more information about creating a server-side proxy, see RPC Encoding and Web Services.
  • You can use a different client-side (InfoPath) data connection, such as the .NET Web service proxy. This is the most flexible and powerful mechanism and also the most complex to develop. It is covered in more detail later in this chapter.

Web Services That Return Untyped XML

Some Web services return untyped XML, such as a method that is defined to return xsd:any. If you know that the Web service always returns data of a particular known schema, you can use a technique similar to that shown in the next section to handle Web services that return XML wrapped in a string.

Web Services That Return XML in a String

Some Web services return XML in a string. This is the other side of the InfoPath feature to submit XML in a string. To use the data as XML, you parse it with code running in the form. Here is one way to create a form that does just that. This is a long procedure that uses a number of temporary files, but the end result is a single, clean template with Visual Basic code-behind. The companion content for this book contains a sample DateTimeWebService that includes a method that returns XML as a string.

The form template you are about to create is a temporary template to get a snapshot of the data returned by the Web service.

To Unwrap an XML String

  1. Launch InfoPath, and then choose Design a Form from the File menu.

  2. Click New From Data Connection.

  3. In the Data Connection Wizard, select Web Service and then click Next.

  4. Select Receive Data, and then click Next.

  5. Enter the URL to the Web service. If you have installed DateTimeWebService from the companion content on your local machine, the URL will be http://localhost/DateTimeWebService/DateTimeLiteral.asmx. Click Next.

  6. Select the GetDateAndTimeAsXmlString operation, click Next, and then click Finish.

    If your Web service does not have input parameters, skip the next step.

  7. Drag and drop the queryFields node into the view on top of the "Drag query fields here" placeholder text. When the shortcut menu appears, select Section With Controls.

  8. Drag and drop the dataFields node into the view on top of the "Drag data fields here" placeholder text. When the shortcut menu appears, select Section With Controls.

  9. Preview the form.

  10. Click Run Query. All of the data returned from the Web service is displayed in the one bound text box in the dataFields section of the form.

  11. Select all of the returned text in the Get Date And Time As XML String Result text box, and then copy it.

  12. Launch your text editor, and then paste the text into it.

  13. Save the resulting XML file to your hard drive, naming it DateTime.xml.

    The XML returned by the Web service might contain information about the encoding used by the Web service. Once the XML file is displayed in InfoPath, Windows will convert everything to Unicode. If you save the XML file in a text editor, be sure to either save the file using the encoding specified in the XML or delete the encoding. For example, if you are using Notepad as your text editor and the XML has a <?xml version="1.0" encoding="utf-16"?> processing instruction, you can remove the encoding="utf-16" attribute or you can make sure the Unicode is selected in the Encoding drop-down list in the Save As dialog box.

  14. Close the text editor. You now have an XML file that represents a sample snapshot of the data returned by the Web service. This file is temporary and is needed only for the next several steps.

  15. Close the preview, and then close the form template. You do not need to save the template.

  16. Launch InfoPath, and choose Design a Form from the File menu.

  17. Click New From XML Document Or Schema.

  18. In the Data Source Wizard, enter the path to the DateTime.xml file you saved, click Next, and then click Finish.

  19. When prompted about whether to use the values in the XML file as the default data in the form, click No. You now have a form that is based on the structure of the data returned by the Web service. However, the new form does not have a connection to the Web service. We will add the connection back as a secondary data connection.

  20. Choose Data Connections from the Tools menu, and then click Add.

  21. In the Data Connections Wizard, select Receive Data and then click Next.

  22. Select Web Service, and then click Next.

  23. Enter the URL to the Web service. This will be the same URL that you used before (http://localhost/DateTimeWebService/DateTimeLiteral.asmx). Click Next.

  24. Select the same operation that you selected before (GetDateAndTimeAsXmlString), and then click Next.

  25. Deselect the Automatically Retrieve Data When Form Is Opened check box, click Finish, and then click Close. You now have a template with a schema that matches the unwrapped string returned by the data connection, and a secondary data connection that has the parameters of, and a connection to, the Web service. The next step is to hook the two together. We will do this using Visual Basic .NET. The same technique can also be used with script.

  26. Save the form template with a temporary name, and then close InfoPath. This form template will be used to build the Visual Basic version of the template.

  27. Launch Visual Studio.

  28. Create a new Visual Basic InfoPath Form Template project. This will be the final product of this exercise. Name the form DateTime.

  29. In the Microsoft Office Project Wizard, select Open Existing Form Template, click Browse, locate and select the temporary form template you saved earlier, and then click Open.

  30. Click Finish, and then click OK. If your Web service does not have input parameters, skip the next four steps.

  31. In the InfoPath designer, choose Data Source from the View menu.

  32. In the Data Source task pane, select your secondary data connection from the Data Source drop-down list.

  33. Drag and drop the queryFields node into the view. When the shortcut menu appears, choose Section With Controls.

  34. Select Main from the Data Source drop-down list.

  35. Add the fields into the view.

  36. Open the Controls task pane, and then add a button to the view.

  37. Right-click the button, and choose Button Properties.

  38. Change the label to Query.

  39. Click Edit Form Code. Focus will return to Visual Studio.

  40. Add the following method to your form code just above the End Class statement:

    Public Sub XmlStringToData(ByVal adapterName As String, _
                               ByVal nodeDest As IXMLDOMNode, _
                               ByVal replace As Boolean)
        Dim dataObj As DataObject
        Dim dataDOM As IXMLDOMDocument2
        Dim xml As IXMLDOMDocument2
        Dim node As IXMLDOMNode
        Dim nodeClone as IXMLDOMNode 
        dataObj = thisXDocument.DataObjects("GetDateAndTimeAsXmlString")
        xml = thisXDocument.CreateDOM() 
    
        ' Run the query.
        dataObj.Query() 
    
        ' Parse the results as XML.
        ' Use IXMLDOMDocument2 to call setProperty.
        dataDOM = dataObj.DOM
        dataDOM.setProperty("SelectionNamespaces", "xmlns:dfs=" & _
     """http://schemas.microsoft.com/office/infopath/2003/dataFormSolution""") 
    
        ' We are assuming that the entire results are a simple string
        ' stored in the grandchild of the dataFields node.
        node = dataDOM.documentElement.selectSingleNode( _
         "/dfs:myFields/dfs:dataFields/node()/node()")
        xml.loadXML(node.text)
    
        ' Copy the values into the destination data source.
        ' You could also perform an XSL or other transform at this point.
        ' If replace is true, this will replace nodeDest with the new values. 
        ' Otherwise this will add the new values as children of nodeDest.
        nodeClone = xml.documentElement.cloneNode(True)
        If (replace) Then
            nodeDest.parentNode.replaceChild(nodeClone, nodeDest)
        Else
            nodeDest.appendChild(nodeClone)
        End If
    End Sub
    
  41. Add the following method call in the button OnClick event handler:

    XmlStringToData("DateTime", thisXDocument.DOM.documentElement, True)
    

    The name of the data adapter is case-sensitive. You can copy and paste the value from the Data Connection Wizard or directly from the name attribute of the xsf:dataObject in the manifest.

  42. Save your project, build it, and then test it.

You now have a complete form that connects to a Web service using the standard InfoPath data connection, transforms the data into XML, and updates the main data source. You can extend this technique for a number of similar situations. You can have the results go into a secondary data connection by adding them as a receive data connection based on the temporary XML file you created and then changing the second parameter of the XmlStringToData call to reference a node in the data connection. You can do more complex transforms, such using an XSL transform, when moving data between data sources. You can also extend this technique to bring data into your form from the .NET code – we'll do this later in the chapter with .NET Web references.

Using .NET Web References

If you want to have maximum control over how a Web service is called, you can use Web references. Web references are proxy classes generated by Visual Studio to connect to a Web service described by WSDL – similar to the Web service adapter used by InfoPath. The disadvantage of using Web references is that there is no InfoPath designer support for them and you have to handle many issues in code.

The following example walks through using a Web reference in a Visual Basic InfoPath project to read from an RPC/encoded Web service that returns a string. It builds on the techniques demonstrated earlier in this chapter. You'll find an example of submitting to a Web service using a Web reference in the "Creating Items in a SharePoint List" section of Chapter 9.

To Create a Secondary Data Connection with a Web Reference

  1. Launch Visual Studio.

  2. Create a new Visual Basic InfoPath form template project named WebRefExample.

  3. In the Visual Studio Solution Explorer, right-click on the WebRefExampleFormCode node, and then choose Add Web Reference.

  4. In the Add Web Reference dialog box, type the URL to your Web service, and then click Go. For this example, we will use a sample RPC/encoded Web service on the Oakleaf Systems Web site. The URL to this Web service is http://oakleaf.ws/AlphaRPC/AlphaRPC.asmx?WSDL. Visual Studio loads the description of the Web service and then displays documentation about it.

  5. Click Add Reference. Your project now has a reference to the Web service that you can call in code. The reference will appear in Solution Explorer, as shown in Figure 7-6. Next you will start to hook it up to the form.

    Web service reference displayed in the Visual Studio Solution Explorer

    Figure 7-6. Web service reference displayed in the Visual Studio Solution Explorer

  6. Choose Open InfoPath from the Project menu.

  7. In the Controls task pane, insert a text box into the view. This will be used as a temporary location to copy the XML from the Web service.

  8. Insert a button into the view.

  9. Right-click the button, and then choose Button Properties.

  10. In the Button Properties dialog box, click Edit Form Code.

  11. Add the following method to your form code just above the End Class statement:

    Public Function QueryAlphaRPCService(ByVal sku As String) As String
        Dim ws As ws.oakleaf.AlphaRPC
        Dim result As String 
    
        ws = New ws.oakleaf.AlphaRPC
        ' The values sent to this example Web service are hard-coded.
        ' See the AlphaRPC Web service documentation for more info.
        result = ws.CheckStock("OAKLEAF-MS7", "AlphaDist", _
                               "AlphaUser", "Alpha#123", _
                               sku, True)
        Return result
    End Function
    
  12. Add the following code into the button OnClick event handler:

    Dim result As String
    ' The values sent to this example Web service are hard-coded.
    ' See the AlphaRPC Web service documentation for more info.
    result = QueryAlphaRPCService("SPKF0496")
    thisXDocument.DOM.selectSingleNode("//my:field1").text = result
    
  13. Press Alt+Shift+F or Ctrl+F5 to preview the form from Visual Studio.

  14. In the InfoPath Preview window, click the button. The text field will display some XML as text. Copy this XML to the Clipboard.

  15. Launch your text editor and then paste the XML into it.

  16. Save the resulting XML file to your hard drive, naming it ceasku.xml. This is a temporary file that will be used only to infer the schema of the data, much like the previous string Web service example.

  17. Close the text editor, and then close the preview.

  18. In the InfoPath designer, choose Data Connections from the Tools menu, and then click Add.

  19. In the Data Connection Wizard, select Receive Data, and then click Next.

  20. Select XML Document, and then click Next.

  21. Enter the path to the ceasku.xml file you just saved, and then click Next.

  22. Deselect the Automatically Retrieve Data When Form Is Opened check box, leave the name of the data connection as ceasku, and then click Finish.

  23. When prompted whether you want to add this file to your form, click No, and then click Close.

  24. In the Data Source task pane, select the ceasku data source.

  25. Insert the AlphaDistInventory node into the view as a Section With Controls.

    You now have a data connection with a schema matching the return value of the Web service and some controls on the form to display the data. The data connection has only schema information and is not is not yet hooked up to any data source. The next step is to connect the data to the data source with code.

  26. Switch to Visual Studio. A dialog box appears asking if you want to automatically update expressions. Click Yes.

  27. Add the following three new lines of code to the button Onclick event handler just above the End Sub statement:

    Dim result As String
    ' The values sent to this example Web service are hard-coded.
    ' See the Web service documentation for more info.
    result = QueryAlphaRPCService("SPKF0496")
    thisXDocument.DOM.selectSingleNode("//my:field1").text = result 
    
    thisXDocument.GetDOM("ceasku").loadXML(result)
    ' Updating data connection values doesn't always update the view.
    thisXDocument.View.ForceUpdate()
    
  28. Save, preview, and then test your solution.

    Note   If you see the error "This DOM cannot be loaded twice" when you call this function, it means InfoPath has already loaded the DOM. Verify that you cleared the Automatically Retrieve Data When Form Is Opened check box in the Data Connection Wizard when you created this data connection.

You now have a template with a secondary data connection. From the point of view of the InfoPath designer, the data is a static file. The code is what hooks the data to the Web service and makes it dynamic.

You can extend this to any type of Web service, including ones with different parameters, and for both sending and receiving data. You can also use the Web reference without binding it to a particular data connection, and instead use the values from the Web service in other ways, such as copying them to individual items in the main data source or for calculations.

Rebinding Web References at Run Time

To rebind a Web reference at run time, set the Url property of the Web service proxy object. You can also control properties such as the credentials, SSL client certificates (for HTTPS), proxy server, and timeout.

Cross-Domain Rules for Calling Web Services

When you call a Web service, you are making a network call. As a result, cross-domain rules come into effect to prevent data from being sent to an undesirable location. In particular, a template using the Domain security model restricts access to InfoPath Web service adapters and certain COM objects, such as the XMLHTTP object. Some of these objects are restricted from calling a cross-domain Web service, depending on the user's Internet Explorer settings, which are typically set to Prompt for the Local Intranet zone, Disable for the Internet zone, and Enable for the Trusted Sites zone. A fully trusted form can access cross-domain data.

The SOAP proxy, built by Visual Studio .NET, and the System.Net.HttpWebRequest object use the .NET settings. By default, this means you can access services on the same domain only as the form is published. You can use a server-side Web service proxy to control data within a domain, although you must be careful when you consider the security aspects of such a proxy. See Chapter 8 for more information about form deployment and security.

Summary

Web services are a mechanism to connect your rich InfoPath form to logic running on a remote server. You learned how to use this connection to build forms and Web services that can be insulated from database and schema changes, how to connect to existing Web services, how to work with some of the advanced features of the InfoPath Web Service Adapter, and how to use Visual Studio .NET Web references to connect to Web services with even more flexibility than is provided by the InfoPath Web Service Adapter.