Export (0) Print
Expand All
35 out of 37 rated this helpful - Rate this topic

Web Service Proxy Wizard

Visual Studio 6.0
 

Brian Berns

July 2001


Download WSP.exe.

Summary: This article describes a new Visual Studio 6.0 wizard that enables developers to wrap a Web service in a proxy component (.dll) that can be used via early binding, just like any other modern COM component.

The article assumes you are familiar with Microsoft Visual Basic 6.0 and Microsoft SOAP Toolkit 2.0. Experience with Microsoft Visual C++ and ATL is useful, but not necessary. (13 printed pages)

Contents

Introduction
Installing the Web Service Proxy Wizard
Using the Web Service Proxy Wizard
Writing a WSP Client
Cross-Platform Interoperability
Conclusion

Introduction

Microsoft® SOAP Toolkit 2.0 can be used to write code that invokes remote Web services—even some that run on non-Microsoft platforms. Unfortunately, when you write such a client today, you lose one of the benefits of modern COM programming: the ability to early-bind to the Web service at design-time as you would to a typical COM server. This article describes a simple new Visual Studio® 6.0 wizard that you can now use to wrap a Web service in a proxy component (.dll) that can be used via early binding just like any other modern COM component. This approach provides a preview of the future of distributed development.

Imagine, for example, that you are the proud recipient of an ActiveX® calculator component that can compute the sum of two given numbers. You fire up Microsoft Visual Basic®, add a reference to the component, and write something like the following code:

Dim oCalculator as New Calculator
MsgBox oCalculator. AddNumbers(3, 4)

Running the program displays the sum (7, in case you were wondering). This is COM programming as it was meant to be—using early (or "v-table") binding. (See Knowledge Base article Q245115 for more information about early vs. late binding in Visual Basic.) In the olden days, however, you couldn't write code like this. Instead, you had to use late binding, as follows:

Dim o Calculator as Object
Set o Calculator = CreateObject("Calculator")
MsgBox oCalculator.AddNumbers(3, 4)

Not only is this code slower and more prone to error (due to a lack of type safety), but it is also incompatible with our beloved IntelliSense. Unfortunately, this is exactly the style of code you must employ when writing a client using the SOAP Toolkit. For example, the equivalent Web service client code looks like this (if you have the SOAP Toolkit sample DocSample1 installed, you should be able to execute this code and see that it works):

Dim Client As New SoapClient
Client.mssoapinit "http://localhost/DocSample1/DocSample1.wsdl"
MsgBox Client.AddNumbers(3, 4)

SoapClient is the SOAP Toolkit class that supports late binding to a Web service, just as Object does in VB COM programming. The SoapClient.mssoapinit method loads the Web service's WSDL (Web service description language) file—this is essentially equivalent to loading a COM type library at runtime. Is it possible to load the WSDL file at design-time instead, just as a COM-aware development environment (such as VB6) would when invoking a modern COM server?

The answer is "yes," and this is where the Web Service Proxy wizard comes in. By providing the location of the WSDL file at design-time rather than at runtime via SoapClient.mssoapinit, you can now easily generate a COM class that wraps the Web service. By coding to this proxy rather than directly to the classes exposed by the SOAP toolkit, you regain the ability to write early-bound code as usual. In fact, the resulting client code is indistinguishable from code you might write to invoke a normal COM server—the fact that you are invoking the SOAP Toolkit runtime and a Web service behind the scenes becomes an implementation detail that is unimportant to you as the client developer.

Installing the Web Service Proxy Wizard

The Web Service Proxy (WSP) wizard is implemented as an "ATL Object Wizard" within Microsoft Visual C++®. As prerequisites, you must:

  • Make sure you have Microsoft Visual C++ 6.0 installed.
  • Make sure you have the Microsoft SOAP Toolkit 2.0 installed. See http://msdn.microsoft.com/xml/ for details.

To install the WSP:

  1. Download the WSP wizard to a temporary folder (for example, C:\Temp\WSP) on your hard drive.
  2. Copy WebServiceProxy.dll from the temporary folder to a permanent location. I suggest placing it in C:\Program Files\Microsoft Visual Studio\Common\MSDev98\Bin\IDE with the other ATL Object Wizard binaries.
  3. Use regsvr32.exe to register WebServiceProxy.dll from its permanent location.
  4. Copy the remaining files (which all have names of the form wsp*.*) from the temporary folder to the folder containing the other ATL Object Wizard templates. Typically, this is C:\Program Files\Microsoft Visual Studio\Common\MSDev98\Template\ATL.

I will happily entertain offers from anyone who would like to write a setup program that performs these steps <grin>. Until then, it's a short, manual effort.

Using the Web Service Proxy Wizard

Let's take a look at how we can invoke the calculator's AddNumbers method via the WSP wizard. The first step is to start Visual C++ and create an empty "ATL COM AppWizard" project. You can also add to an existing ATL project if you'd prefer. I've chosen to call the example project "CalcWSP" for the purposes of this article. Once you have an ATL project open, choose the Insert | New ATL Object menu item to invoke the ATL Object Wizard. If you have installed WSP correctly, you should see Web Service Proxy listed in the Objects category.

Figure 1. Choosing the ATL Object wizard

Choose the WSP icon and click Next. Type the URL of the Web service's WSDL file in the next dialog and click the Load button. The WSP wizard will retrieve the WSDL file from the Web service's server and populate the rest of the dialog with default values.

Figure 2. Modifying the ATL Object wizard properties

If you do not like the wizard-generated proxy name, you can change it to something simpler, like "CalcProxy." When you click on OK, the WSP wizard will auto-generate a proxy class using the specified WSDL file. The generated COM interface is called ICalcProxy and the corresponding implementation class is called CCalcProxy. You can examine the implementation of CCalcProxy::AddNumbers in CalcProxy.cpp:

   // create the request
   ISoapConnectorPtr spConnector(__uuidof(HttpConnector));
   spConnector->Property[L"EndPointURL"] = m_url;
   if (m_proxyServerAddress.length() > 0) {
      spConnector->Property[L"ProxyServer"] = m_proxyServerAddress;
      spConnector->Property[L"ProxyPort"] = m_proxyServerPort;
   }
   spConnector->Property[L"SoapAction"] =
"http://tempuri.org/action/Sample1.AddNumbers";
   ISoapSerializerPtr spSerializer(__uuidof(SoapSerializer));
   spSerializer->Init((IUnknown *) spConnector->InputStream);
   spConnector->BeginMessage();
   spSerializer->startEnvelope(L"", 
            L"http://schemas.xmlsoap.org/soap/encoding/",
L"");
   spSerializer->SoapAttribute(L"xsi", L"",
L"http://www.w3.org/2001/XMLSchema-instance", L"xmlns");
   spSerializer->SoapAttribute(L"xsd", L"",
L"http://www.w3.org/2001/XMLSchema", L"xmlns");
   spSerializer->startBody(L"NONE");
   spSerializer->startElement("AddNumbers", "http://tempuri.org/message/", 
            L"NONE",
L"");
   spSerializer->startElement(L"NumberOne", L"", L"NONE", L"");
   spSerializer->SoapAttribute(L"xsi:type", L"", L"xsd:double", L"");
   spSerializer->writeString((_bstr_t) (_variant_t) NumberOne);
   spSerializer->endElement();
   spSerializer->startElement(L"NumberTwo", L"", L"NONE", L"");
   spSerializer->SoapAttribute(L"xsi:type", L"", L"xsd:double", L"");
   spSerializer->writeString((_bstr_t) (_variant_t) NumberTwo);
   spSerializer->endElement();
   spSerializer->endElement();
   spSerializer->endBody();
   spSerializer->endEnvelope();
   spConnector->EndMessage();

      // parse the response
   ISoapReaderPtr spReader(__uuidof(SoapReader));
   VARIANT_BOOL flag = spReader->Load((IUnknown *) spConnector->OutputStream, 
            L"");
   if ((flag == VARIANT_TRUE) && (spReader->Fault == NULL)) {
      _bstr_t s_Result = spReader->RPCParameter[L"Result"][L""]->text;
      *Result = (_variant_t) s_Result;
   }

The details of how this works are not too important here. Basically, the wizard-generated code is simply calling the SOAP Toolkit's "low-level API" to invoke the remote Web service. Using the low-level API gives the wizard the fine control it needs to create proxies that interoperate with many different SOAP server platforms. For example, the low-level SoapSerializer.SoapAttribute method is used to specify the XSD datatype of each parameter passed to the Web service (as required by some SOAP servers). The SoapAttribute method is also used to specify the XSD and XSI namespaces that are required by some SOAP servers.

Before compiling the WSP code, you must tell Visual C++ to enable exception handling for your project. To do this, first choose the Project | Settings menu item. In the resulting dialog, make sure to choose All Configurations in the Settings For field. Then click on the C/C++ tab and choose the C++ Language category. Click the Enable exception handling check box and then click the OK button.

Figure 3. Turning on exception handling in project settings window

Once you've turned on exception handling, you're ready to compile the project by simply pressing the F7 key. Visual C++ will then build and register CalcWSP.dll for you.

Congratulations, you have constructed your first Web service proxy component!

Writing a WSP Client

Using the wizard-generated component is just as easy as creating it. From Visual Basic, for example, first open a project and create a reference to the proxy component. Note that you do not need to create a reference to the SOAP Toolkit's type library (MSSOAP1.dll).

Figure 4. ReferencesProject1 window

If you then open the VB Object Browser, you can see that all the Web service's methods are now available at design-time, restoring type safety and IntelliSense support while eliminating the need to load the service's WSDL file at runtime.

Figure 5. Object browser window

We can now write early-bound code to invoke the Web service. For example, place a command button on a form and use the following code in its event handler:

Private Sub Command1_Click()

Dim oCalc As New CalcProxy
MsgBox oCalc.AddNumbers(3, 4)

End Sub

Note that the service's other methods, EchoString and SubtractNumbers, can also be invoked using early binding. When you run the project, clicking the command button should again display the sum (yes, it's still 7).

In case you are invoking the Web service from behind a proxy server, you can specify its address and port as properties of the CalcProxy object. You can also specify a different target URL in case the Web service moves to a new location.

Note that the WSP component can automatically translate a SOAP "fault" response into a COM error that can be handled with Visual Basic's built-in error handling mechanism. For example, suppose you raise the following error in the server's implementation of AddNumbers:

Err.Raise vbObjectError + 1, , "Service not available"

Then try to invoke the service again using the following client code:

Private Sub Command1_Click()

On Error GoTo Handler

    Dim oCalc As New CalcProxy
    MsgBox oCalc.AddNumbers(3, 4)

Exit Sub

Handler:
    MsgBox Err.Description, vbCritical, "Error"

End Sub

Since the service now rejects the call, a SOAP "fault" response is sent back to the client:

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode> 
<faultstring>
WSDLOperation: Executing method AddNumbers failed
</faultstring> 
<faultactor>
http://tempuri.org/action/Sample1.AddNumbers
</faultactor> 
<detail>
<mserror:errorInfo
xmlns:mserror="http://schemas.microsoft.com/
soap-toolkit/faultdetail/error/">
<mserror:returnCode>-2147221503</mserror:returnCode> 
<mserror:serverErrorInfo>
<mserror:description>
Service not available
</mserror:description>
<mserror:source>DocSample1</mserror:source> 
</mserror:serverErrorInfo>
<mserror:callStack>
<mserror:callElement>
<mserror:component>WSDLOperation</mserror:component>
<mserror:description>
Executing method AddNumbers failed
</mserror:description> 
<mserror:returnCode>-2147352567</mserror:returnCode> 
</mserror:callElement>
</mserror:callStack>
</mserror:errorInfo>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The WSP component automatically converts the faultstring element into a COM error, which is handled by the Visual Basic client code via a message box:

Figure 6. WSDL operation error

Cross-Platform Interoperability

One of the major reasons for the popularity of SOAP as the foundation for Web services is the promise of cross-platform interoperability. If widely adopted, SOAP will fold the benefits of seamless cross-platform Web surfing (which we all now take for granted) back into the programming world. (Note that if all we were concerned with was remote invocation of Windows Web services, we could all use COM Internet Servces—CIS, also known as "DCOM over HTTP"—and be done with it.)

One interesting collection of non-Windows Web services is maintained by XMethods. In this section, I'll demonstrate how to invoke such a Web service using WSP. The Web service I'll use is the "Weather" service described at http://www.xmethods.net/detail.html?id=8. Note that this service is written in Java running under an Apache Web server on a Solaris box. In fact, this service cannot be invoked using the SOAP Toolkit's late-binding "high-level API" because of incompatibilities in the specification of parameter datatypes between the two systems. The WSP wizard generates code that does not have this limitation (because it uses the toolkit's "low-level API" instead).

To call this Web service, once again open or create an ATL project (I will continue to use the CalcWSP project) and invoke the WSP wizard.

Figure 7. ATL Object wizard properties

Clicking OK will auto-generate an ATL C++ class called CTemperaturePortProxy, which implements a COM interface called ITemperaturePortProxy. As before, we can now build and use the WSP component. In this case, the client code would look something like the following:

Private Sub Command1_Click()

Dim oTemperature As New TemperaturePortProxy
MsgBox oTemperature.getTemp("20816")

End Sub

This client requests the Web service to return the current temperature in the 20816 Zip code. It's interesting to observe the SOAP request and response messages in this case (captured using the SOAP Toolkit's trace utility). Here's the request message:

POST /soap/servlet/rpcrouter HTTP/1.1
Content-Type: text/xml
Host: services.xmethods.net
SOAPAction: ""
Content-Length: 474

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope
      SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
      xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
      xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
      xmlns:xsd="http://www.w3.org/1999/XMLSchema">
   <SOAP-ENV:Body>
      <SOAPSDK1:getTemp xmlns:SOAPSDK1="urn:xmethods-Temperature">
         <zipcode xsi:type="xsd:string">20816</zipcode>
      </SOAPSDK1:getTemp>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

And the corresponding response from XMethods:

Date: Tue, 01 May 2001 04:14:21 GMT
Status: 200
Servlet-Engine: Tomcat Web Server/3.1 (JSP 1.1; Servlet 2.2; Java 1.3.0; 
      SunOS 5.8 sparc; java.vendor=Sun Microsystems Inc.)
Content-Type: text/xml; charset=utf-8
Content-Length: 465
Content-Language: en
Server: Electric/1.0
Electric-Routing: true
Age: 4
Via: HTTP/1.1 cluster.lnh.md (Traffic-Server/4.0.9 [cMsSf ])

<?xml version="1.0" encoding="UTF-8" ?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:getTempResponse
xmlns:ns1="urn:xmethods-Temperature"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<return xsi:type="xsd:float">59.0</return> 
</ns1:getTempResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

As you can see from the response, the Weather Web service is certainly not implemented using Microsoft's SOAP Toolkit. Nonetheless, the two platforms interoperate seamlessly. Note, for example, that the Weather Web service uses the 1999 XML Schema namespace URI rather than the 2001 version favored by the SOAP Tookit. The wizard automatically attempts to detect and use the correct XSD namespace when it loads the service's WSDL file. This allows the wizard to build proxies that work with both older and newer Web services.

Keep in mind that there are some SOAP features that the WSP wizard cannot currently handle. For example, the wizard cannot create a proxy for a service that defines a complex type in its WSDL interface. It also creates illegal C++ for any WSDL identifiers that are not also legal C++ identifiers (e.g. an parameter name that contains a '-' character). Because it is built on the SOAP Toolkit and the MSXML parser, the WSP wizard also cannot handle WSDL files that are rejected by either of those technologies.

Conclusion

The examples we've covered in this article demonstrate the kind of client-side COM proxy you can build with the Web Service Proxy wizard. With the number of public SOAP Web services ballooning daily (check out XMethods for a very diverse list), the WSP wizard makes it a bit easier to keep up by creating robust client-side proxy components with a few clicks of the mouse.

This article was written and developed by Brian Berns. Brian is a Consultant for RDA, a software engineering firm using advanced technologies to build complex, custom software systems that enable its clients to conduct business electronically.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.