Académique Documents
Professionnel Documents
Culture Documents
NET Framework
Building a Web Service Client using C# and .NET Gopalan Suresh Raj Note
To work with any of these samples, you will need the following:
.........................................Microsoft .NET SDK .........................................Microsoft Visual Studio.NET Beta 2 or higher
The above command creates a proxy for the OIDServer web service from the WSDL document obtained from the URL http://localhost/OIDServer/OIDServer.asmx?WSDL. The proxy uses SOAP as its protocol to talk to the web service and is generated as a C# source file which is shown below for your perusal.
OIDServer.cs
1: 2: 3: 4: 5: 6: 7: 8: 9: 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 : 24 : 25 : 26 : 27 : 28 : 29 : 30 : 31 : //-----------------------------------------------------------------------------// <autogenerated> // This code was generated by a tool. // Runtime Version: 1.0.2914.16 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </autogenerated> //-----------------------------------------------------------------------------// // This source code was auto-generated by wsdl, Version=1.0.2914.16. // using System.Diagnostics; using System.Xml.Serialization; using System; using System.Web.Services.Protocols; using System.Web.Services; [System.Web.Services.WebServiceBindingAttribute(Name="OIDServerSoap", Namespace="http://icommware.com")] public class OIDServer : System.Web.Services.Protocols.SoapHttpClientProtocol { [System.Diagnostics.DebuggerStepThroughAttribute()] public OIDServer() { this.Url = "http://localhost/OIDServer/OIDServer.asmx"; } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://icommware.com/ge nerateOID", RequestNamespace="http://icommware.com", ResponseNamespace="http://icommware.com", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] public string generateOID() { object[] results = this.Invoke("generateOID", new object[0]); return ((string)(results[0])); } [System.Diagnostics.DebuggerStepThroughAttribute()] public System.IAsyncResult BegingenerateOID(System.AsyncCallback callback, object asyncState) { return this.BeginInvoke("generateOID", new object[0], callback, asyncState); } [System.Diagnostics.DebuggerStepThroughAttribute()] public string EndgenerateOID(System.IAsyncResult asyncResult) {
32 object[] results = this.EndInvoke(asyncResult); : return ((string)(results[0])); 33 } : } 34 : 35 : 36 : 37 : 38 : 39 : 40 : 41 : 42 : 43 : 44 : 45 : 46 : 47 : 48 : 49 : 50 : 51 : The wsdl.exe utility can also take a WSDL file as input instead of a URL pointing to the location where the WSDL can be obtained. This C# proxy source file represents the proxy class for the OIDServer web service that clients can compile against. If you examine the above class, you will notice that it contains an OIDServer proxy class that derives from the System.Web.Services.Protocols.SoapHttpClientProtocol class. If you use the /protocol:HttpGet or /protocol:HttpPost parameters, the OIDServer derives from either the System.Web.Services.Protocols.HttpGetClientProtocol class or the System.Web.Services.Protocols.HttpPostClientProtocol class.
We can compile the C# source file into a dynamic link library (DLL) and then add a reference to this DLL to any project you want to create. Compile the proxy class as a DLL as shown below: Command Prompt
C:\MyProjects\Cornucopia\WebService\Client>csc /t:library /r:System.Web.Services.dll /r:System.Xml.dll OIDServer.cs Microsoft (R) Visual C# Compiler Version 7.00.9254 [CLR version v1.0.2914] Copyright (C) Microsoft Corp 2000-2001. All rights reserved. C:\MyProjects\Cornucopia\WebService\Client>
Client.cs
1: 2: 3: 4: 5: 6: ////////////////////////////////////////////////////// /// The following example illustrates a Client to a /// WebService developed using C# and the .NET Framework. /// /// author: Gopalan Suresh Raj /// Copyright (c), 2002. All Rights Reserved.
7: 8: 9: 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 : 24 : 25 : 26 : 27 : 28 : 29 : 30 : 31 : 32 : 33 : 34 : 35 :
/// URL: http://gsraj.tripod.com/ /// email: gopalan@gmx.net /// /// <generate> /// wsdl /l:CS /protocol:SOAP http://localhost/OIDServer/OIDServer.asmx?WSDL /// wsdl /l:CS /protocol:HttpGet http://localhost/OIDServer/OIDServer.asmx?WSDL /// wsdl /l:CS /protocol:HttpPost http://localhost/OIDServer/OIDServer.asmx?WSDL /// </generate> /// <compile> /// csc /t:library /r:System.Web.Services.dll /r:System.Xml.dll OIDServer.cs /// </compile> /// <compile> /// csc Client.cs /r:OIDServer.dll /// </compile> ////////////////////////////////////////////////////// using System; namespace TestOIDServer { /// <summary> /// Summary description for Client. /// </summary> public class Client { /// <summary> /// Default No argument constructor /// </summary> public Client() { } /// <summary> /// Entry Point to this Application /// </summary> public static void Main () { // Create a proxy OIDServer server = new OIDServer(); // Invoke generateOID() over SOAP and get the new OID string oid = server.generateOID (); // Print out the value Console.WriteLine ("The new OID is :"+oid); } } }
36 : 37 : 38 : 39 : 40 : 41 : 42 : 43 : 44 : 45 : 46 : 47 : 48 : 49 :
2. Develop the OIDServer.asmx file and the OIDServer.asmx.cs class file 3. Modify the generated AssemblyInfo.cs to add the right assembly information
4. Build the Project Files
OIDServer.asmx
1 <%@ WebService Language="c#" Codebehind="OIDServer.asmx.cs" : Class="OIDServer.OIDServer" %> People familiar with ASP will immediately recognize the @ symbol in front of the WebService keyword. The WebService directive specifies the language used to develop the web service so that ASP.NET can compile this service with the right compiler. The Class="OIDServer.OIDServer" directive specifies the class that implements the web service, so that the right class can be loaded and its Web Services Description (WSDL) generated using reflection. The OIDServer.asmx.cs Class file
As shown on Line 34, Web Services derive from the System.Web.Services.WebService class which allows the web service object to access all normal ASP objects exposed in the WebService base class. This means, the OIDServer class can use ASP objects as though it were a regular ASP based application. As the default namespace http://tempuri.org/ will not uniquely identify our web service from other web services on the internet, we specify our own namespace http://icommware.com before publishing our web service publicly as shown on Line 32. The Namespace = "http://icommware.com" attribute facilitates us to specify our own namespace. Unless you don't want to publish a method, you need to tag all public methods with the WebMethod attribute to make web methods public methods of a distributed component that are accessible though the world-wide web. This is shown on line 64.
OIDServer.asmx.cs
1: 2: 3: 4: 5: 6: 7: 8: 9: 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 : 24 : 25 : 26 ////////////////////////////////////////////////////// /// The following example illustrates a Web Service /// developed using C# and the .NET Framework. /// /// author: Gopalan Suresh Raj /// Copyright (c), 2002. All Rights Reserved. /// URL: http://gsraj.tripod.com/ /// email: gopalan@gmx.net /// /// <generate> /// wsdl /l:CS /protocol:SOAP http://localhost/OIDServer/OIDServer.asmx?WSDL /// wsdl /l:CS /protocol:HttpGet http://localhost/OIDServer/OIDServer.asmx?WSDL /// wsdl /l:CS /protocol:HttpPost http://localhost/OIDServer/OIDServer.asmx?WSDL /// </generate> /// <compile> /// csc /t:library /r:System.Web.Services.dll /r:System.Xml.dll OIDServer.dll /// </compile> ////////////////////////////////////////////////////// using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; using System.EnterpriseServices; namespace OIDServer { /// <summary> /// Summary description for OIDServer. /// </summary> [WebService ( Namespace = "http://icommware.com", Name = "OIDServer", Description = "Generates unique Object Identity (OID)" )] public class OIDServer : System.Web.Services.WebService { /// <summary> /// Default No argument constructor /// </summary> public OIDServer() { //CODEGEN: This call is required by the ASP.NET Web Services Designer InitializeComponent(); }
: 27 #region Component Designer generated code : /// <summary> 28 /// Required method for Designer support - do not modify : /// the contents of this method with the code editor. 29 /// </summary> : private void InitializeComponent() { 30 } : #endregion 31 : /// <summary> 32 /// Clean up any resources being used. : /// </summary> 33 protected override void Dispose( bool disposing ) { : } 34 : /// <summary> 35 /// Generates unique Object Identity (OID). Returns a String representation : /// of the value of a new GUID in the Windows Registry format. 36 /// </summary> : /// <returns></returns> 37 [WebMethod( EnableSession = true, TransactionOption = TransactionOption.Disabled, : BufferResponse = false, CacheDuration = 0, MessageName = 38 "generateOID", : Description = "Generates unique Object Identity (OID)" )] 39 public string generateOID () { : return Guid.NewGuid ().ToString(); 40 } : } 41 } : 42 : 43 : 44 : 45 : 46 : 47 : 48 : 49 : 50 : 51 : 52 : 53 :
54 : 55 : 56 : 57 : 58 : 59 : 60 : 61 : 62 : 63 : 64 : 65 : 66 : 67 : 68 : 69 : 70 : 71 : Notice that the generateOID() method on Line 64 is tagged with the WebMethod tag using the [] syntax. By specifying the [WebMethod] tag in front of this public method, we make this public method callable from any Internet client. Behind the scenes, the generateOID() method is associated with an attribute which is implemented as a WebMethodAttribute class. This class has six properties which are also specified from Lines 64 through 66. The EnableSession property enables or disables session state. If you don't intend to use session state for this web method, you should disable this flag so that the web server does not generate and manage session IDs for every user accessing this web method. This might improve performance. This flag is true by default. The TransactionOption property can be one of five values: Disabled, NotSupported, Supported, Required, and RequiresNew. As Web Methods can participate only as the root object in a Transaction, both Required and RequiresNew options result in a new transaction being created for the Web Method. The Disabled, NotSupported, and Supported options result in no transactions being used for the web methods. The TransactionOption property is set to Disabled by default.
The BufferResponse property controls whether or not to buffer the method's response. The CacheDuration property specifies the length of time, in seconds, to keep the method response in the cache. The default does not hold the response in the cache (0 seconds). The MessageName property is used to distinguish web methods with the same names. For example, if you have two methods with the same name, and you want to publish both these methods as web methods, the system will have a problem identifying one from the other as their names are duplicated. This property ensures that all service signatures in the WSDL are unique. The Description property provides additional descriptive information about a particular method.
information
You provide the compiler with your assembly information in an assembly file called AssemblyInfo.cs. The assembly information file is compiled with the rest of the project's source files. The information is in the form of assembly attributes - directives to the compiler on the information to embed in the assembly.
AssemblyInfo.cs
1: 2: 3: 4: 5: 6: 7: 8: 9: 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 using System.Reflection; using System.Runtime.CompilerServices; // // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. // [assembly: AssemblyTitle("OID Server")] [assembly: AssemblyDescription("Creates unique Object Identity (OID)")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("eCommWare Corporation")] [assembly: AssemblyProduct("OID Server")] [assembly: AssemblyCopyright("(c) 2001, Gopalan Suresh Raj. All Rights Reserved.")] [assembly: AssemblyTrademark("Web Cornucopia")] [assembly: AssemblyCulture("en-US")] // // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: [assembly: AssemblyVersion("1.0.*")] // // In order to sign your assembly you must specify a key to use. Refer to the
: 22 : 23 : 24 : 25 : 26 : 27 : 28 : 29 : 30 : 31 : 32 : 33 : 34 : 35 : 36 : 37 : 38 : 39 : 40 : 41 : 42 : 43 : 44 : 45 : 46 : 47 : 48 :
// Microsoft .NET Framework documentation for more information on assembly signing. // // Use the attributes below to control which key is used for signing. // // Notes: // (*) If no key is specified, the assembly is not signed. // (*) KeyName refers to a key that has been installed in the Crypto Service // Provider (CSP) on your machine. KeyFile refers to a file which contains // a key. // (*) If the KeyFile and the KeyName values are both specified, the // following processing occurs: // (1) If the KeyName can be found in the CSP, that key is used. // (2) If the KeyName does not exist and the KeyFile does exist, the key // in the KeyFile is installed into the CSP and used. // (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. // When specifying the KeyFile, the location of the KeyFile should be // relative to the "project output directory". The location of the project output // directory is dependent on whether you are working with a local or web project. // For local projects, the project output directory is defined as // <Project Directory>\obj\<Configuration>. For example, if your KeyFile is // located in the project directory, you would specify the AssemblyKeyFile // attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] // For web projects, the project output directory is defined as // %HOMEPATH%\VSWebCache\<Machine Name>\<Project Directory>\obj\<Configuration>. // (*) Delay Signing is an advanced option - see the Microsoft .NET Framework // documentation for more information on this. // [assembly: AssemblyDelaySign(false)] [assembly: AssemblyKeyFile("")] [assembly: AssemblyKeyName("")]
49 : 50 : 51 : 52 : 53 : 54 : 55 : 56 : 57 : 58 : 59 : 60 : 61 : 62 : The OIDServer.vsdisco file Whenever a remote (or local) client is interested in using a Web Service, the first step is to determine which web services exist on a given machine. Even though the .NET class library provides the API that allows you to examine registered Web Services programmatically, discovery services are also required by numerous case tools. The .vsdisco file is used to describe each Web Service in a given virtual directory and any related subfolders.
OIDServer.vsdisco
1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 <?xml version="1.0" ?> <dynamicDiscovery xmlns="urn:schemas-dynamicdiscovery:disco.2000-03-17"> <exclude path="_vti_cnf" /> <exclude path="_vti_pvt" /> <exclude path="_vti_log" /> <exclude path="_vti_script" /> <exclude path="_vti_txt" /> <exclude path="Web References" /> </dynamicDiscovery>
: 9 : The Web.config file The generated Web.config file should normally be placed in the same directory as the .asmx file. This configuration file allows you to control various application settings of the virtual directory. To ease development and testing, it is recommended that you set the authentication mode to None as shown on Line 28. However, when releasing the web service to the public, you can change it to either Windows, or Forms, or Passport, as opposed to None. The authentication mode set to Windows indicates that authentication is performed by IIS in one of three ways - basic, digest, or Integrated Windows Authentication. The authentication mode set to Forms indicates that un-authenticated requests are redirected to a login page. The authentication mode set to Passport indicates that un-authentication requests are directed to Microsoft's centralized authentication service. When authenticated, a token is passed back and used by subsequent requests.
Web.config
1: 2: 3: 4: 5: 6: 7: 8: 9: 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 <?xml version="1.0" encoding="utf-8" ?> <configuration> <system.web> <!-- DYNAMIC DEBUG COMPILATION Set compilation debug="true" to enable ASPX debugging. Otherwise, setting this value to false will improve runtime performance of this application. Set compilation debug="true" to insert debugging symbols (.pdb information) into the compiled page. Because this creates a larger file that executes more slowly, you should set this value to true only when debugging and to false at all other times. For more information, refer to the documentation about debugging ASP.NET files. --> <compilation defaultLanguage="c#" debug="true" /> <!-- CUSTOM ERROR MESSAGES Set mode="on" or "remoteonly" to enable custom error messages, "off" to disable. Add <error> tags for each of the errors you want to handle. --> <customErrors mode="Off" /> <!-- AUTHENTICATION This section sets the authentication policies of the application. Possible modes are "Windows", "Forms", "Passport" and "None" --> <authentication mode="None" />
: 21 : 22 : 23 : 24 : 25 : 26 : 27 : 28 : 29 : 30 : 31 : 32 : 33 : 34 : 35 : 36 : 37 : 38 : 39 : 40 : 41 : 42 : 43 : 44 : 45 : 46 : 47 :
<!-- APPLICATION-LEVEL TRACE LOGGING Application-level tracing enables trace log output for every page within an application. Set trace enabled="true" to enable application trace logging. If pageOutput="true", the trace information will be displayed at the bottom of each page. Otherwise, you can view the application trace log by browsing the "trace.axd" page from your web application root. --> <trace enabled="false" requestLimit="10" pageOutput="false" traceMode="SortByTime" localOnly="true" /> <!-- SESSION STATE SETTINGS By default ASP.NET uses cookies to identify which requests belong to a particular session. If cookies are not available, a session can be tracked by adding a session identifier to the URL. To disable cookies, set sessionState cookieless="true". --> <sessionState mode="InProc" stateConnectionString="tcpip=127.0.0.1:42424" sqlConnectionString="data source=127.0.0.1;user id=sa;password=" cookieless="false" timeout="20" /> <!-- PREVENT SOURCE CODE DOWNLOAD This section sets the types of files that will not be downloaded. As well as entering a httphandler for a file type, you must also associate that file type with the aspnet_isapi.dll in the App Mappings property of the web site, or the file can be downloaded. It is recommended that you use this section to prevent your sources being downloaded. --> <httpHandlers> <add verb="*" path="*.vb" type="System.Web.HttpNotFoundHandler,System.Web" /> <add verb="*" path="*.cs" type="System.Web.HttpNotFoundHandler,System.Web" /> <add verb="*" path="*.vbproj" type="System.Web.HttpNotFoundHandler,System.Web" /> <add verb="*" path="*.csproj" type="System.Web.HttpNotFoundHandler,System.Web" /> <add verb="*" path="*.webinfo" type="System.Web.HttpNotFoundHandler,System.Web" /> </httpHandlers> <!-- GLOBALIZATION This section sets the globalization settings of the application. --> <globalization
: 76 :
5. Deploy the Web Service files on IIS, and test your Web Service
As soon as you compile your Web Service, you can use it by executing it from the Visual Studio.NET IDE - Run or Debug the Application. By default, the machine's active browser functions as a makeshift client, that shows a HTML view of all the methods marked with the [WebMethod] attribute as shown in the figure below.
You can invoke the generateOID() method by clicking the generateOID link and you'll get the following page shown.
When you click on the Invoke button, the web service is invoked and the result is displayed on the next page.
1. Develop a Class that generates the SOAP message Body element - the
ApacheMessageBody.java class file
2. Develop a Class that parses the SOAP response - the SAXHandler.java class file 3. Develop a Java Proxy for the .NET endpoint - the ApacheSoapProxy.java class file 4. Develop the Actual Client - Client.java class file
5. Build and Run
Apache SOAP performs serialization and deserialization using a class called Body, which uses its marshall() and unmarshall() methods to perform the respective operations. While marshall() is an instance method, unmarshall() is a static class method. This means, it is not possible to just inherit from the Body class and override the unmarshall() method. However, you can change the Serialization behavior of the Body class by overriding the marshall() method as shown from Lines 30 through 51 in the code below. Depending on the number of methods that you may have, you can either create multiple Body implementations, each with their own marshall() methods, or you may choose to build one Body implementation with a single marshall()method which can choose the right code to execute based on other information. Since ASP.NET endpoints use document/literal encoding as opposed to RPC encoding, you only need to write out the following information: Write out the Body Element (as shown on Lines 36-39 ) Write out the method name and namespace information (as shown on Line 42 ) Write out the arguments that are passed into the method (as shown on Lines 43 )
Notice that in the code below, we extend the Body class as shown on line 20
ApacheMessageBody.java
1: 2: 3: 4: 5: 6: 7: 8: 9: 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : ////////////////////////////////////////////////////// /// This Class generates the SOAP message Body element /// /// author: Gopalan Suresh Raj /// Copyright (c), 2002. All Rights Reserved. /// URL: http://gsraj.tripod.com/ /// email: gopalan@gmx.net ////////////////////////////////////////////////////// import import import import import java.io.*; org.apache.soap.*; org.apache.soap.util.*; org.apache.soap.util.xml.*; org.apache.soap.rpc.SOAPContext;
/** * This Class generates the SOAP message Body element * @author Gopalan Suresh Raj */ public class ApacheMessageBody extends Body { /** potential argument to the web method. */ //public String inputString_; /** * Override the Apache default marshall method * and change how the SOAP Body element * is serialized. */ public void marshall (String inScopeEncodingStyle, Writer sink, NSStack nameSpaceStack, XMLJavaMappingRegistry registry,
22 SOAPContext context) throws IllegalArgumentException, IOException : { 23 // Set the Body element : String soapEnvironmentNamespacePrefix = "SOAP-ENV"; 24 sink.write ('<'+soapEnvironmentNamespacePrefix+':'+ : Constants.ELEM_BODY+'>'+ 25 StringUtils.lineSeparator); : 26 // Write out the method name and related argument (s) : sink.write ("<generateOID xmlns=\""http://tempuri.org/\">"+ 27 //"<inputString>"+inputString_+"</inputString>"+ : "</generateOID>"); 28 : // Close the Body element 29 sink.write ("</" + soapEnvironmentNamespacePrefix+':'+ : Constants.ELEM_BODY+'>'+ 30 StringUtils.lineSeparator); : nameSpaceStack.popScope (); 31 } : } 32 : 33 : 34 : 35 : 36 : 37 : 38 : 39 : 40 : 41 : 42 : 43 : 44 : 45 : 46 : 47 : 48 : 49
: 50 : 51 : 52 : Now that you can send the message, you should be able to read the message. To read the message, you need a class that can handle SAX Processing for you and read the data.
SAXHandler.java
1: 2: 3: 4: 5: 6: 7: 8: 9: 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 ////////////////////////////////////////////////////// /// This Class parses the SOAP response /// /// author: Gopalan Suresh Raj /// Copyright (c), 2002. All Rights Reserved. /// URL: http://gsraj.tripod.com/ /// email: gopalan@gmx.net ////////////////////////////////////////////////////// import org.xml.sax.*; import org.xml.sax.helpers.*; /** * This Class parses the SOAP response. This is a * general purpose class that can obtain any single * element response. * * @author Gopalan Suresh Raj */ public class SAXHandler extends DefaultHandler { private String indent_ = ""; private String result_ = ""; private String elementToSearchFor_ = ""; private boolean foundResult_ = false; /** Default No argument constructor */ public SAXHandler () { }
: /** 21 * Retrieve the result for a single element : * The code has to be modified as necessary 22 * for more complex types and arrays : */ 23 public String getResult () { : return result_; 24 } : 25 /** Provide the element name to search for */ : public void setElementToSearchFor (String elementName) { 26 elementToSearchFor_ = elementName; : } 27 : /** Retrieve the set element name */ 28 public String getElementToSearchFor () { : return elementToSearchFor_; 29 } : 30 /** : * Overriden method of the DefaultHandler class to 31 * gain notification of all SAX events : */ 32 public void startElement (String namespaceURI, : String localName, 33 String qName, : Attributes attributes) throws SAXException { 34 if (foundResult_ == false) { : foundResult_ = (localName.compareTo (elementToSearchFor_) == 0); 35 } : } 36 : /** 37 * Overriden method of the DefaultHandler class to : * gain notification of all SAX events 38 */ : public void characters (char[] characters, int start, int length) throws SAXException { 39 if (foundResult_ == true) { : result_ = String.valueOf (characters, start, length); 40 foundResult_ = false; : } 41 } : 42 } : 43 : 44 : 45 : 46 : 47 :
48 : 49 : 50 : 51 : 52 : 53 : 54 : 55 : 56 : 57 : 58 : 59 : 60 : 61 : 62 : 63 : 64 : 65 : 66 : 67 : 68 : 69 : 70 : 71 : 72 : 73 : 74 :
Since Apache SOAP 2.2 classes encode messages using RPC encoding, for .NET interoperability which uses document/literal encoding, you must override the code that constructs the body in addition to the code that interprets the response.
ApacheSoapProxy.java
1: 2: 3: 4: 5: 6: 7: 8: 9: 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 ////////////////////////////////////////////////////// /// This Class creates a Java Proxy for the .NET endpoint /// /// author: Gopalan Suresh Raj /// Copyright (c), 2002. All Rights Reserved. /// URL: http://gsraj.tripod.com/ /// email: gopalan@gmx.net ////////////////////////////////////////////////////// import import import import import import import import java.io.*; java.net.*; javax.activation.*; org.apache.soap.*; org.apache.soap.messaging.*; org.xml.sax.*; org.xml.sax.helpers.*; org.apache.xerces.parsers.SAXParser;
/** * This Class creates a Java Proxy for the .NET endpoint * @author Gopalan Suresh Raj */ public class ApacheSoapProxy { private URL url_ = null; private String soapActionUri_ = ""; private Message message_ = new Message (); private Envelope envelope_ = new Envelope (); DataHandler soapMessage_ = null; /** Default No argument constructor */ public ApacheSoapProxy () throws MalformedURLException { this.url_ = new URL ("http://localhost/OIDServer/OIDServer.asmx"); }
: /** Set the End Point URL */ 23 public synchronized void setEndPoint (URL url) { : this.url_ = url; 24 } : 25 /** Retrieve the End Point URL */ : public synchronized URL getEndPoint () { 26 return this.url_; : } 27 : /** 28 * Apache 2.2 classes encode messages differently than .NET does. : * Therefore we have to override the piece that builds the body and 29 * the pieces that interpret the response. : */ 30 public synchronized String generateOID () throws SOAPException { : String returnValue = ""; 31 : if (this.url_ == null) { 32 throw new SOAPException (Constants.FAULT_CODE_CLIENT, : "A URL must be specified through "+ 33 "ApacheSoapProxy.setEndPoint(URL)"); : } 34 // Get this from the soapAction attribute on the : // soap:operation element that is found within the SOAP 35 // binding information in the WSDL : this.soapActionUri_ = "http://icommware.com/generateOID"; 36 ApacheMessageBody ourBody = new ApacheMessageBody (); : 37 // Set the argument : //theBody.inputString_ = ""; 38 : // Replace the default body with our own 39 this.envelope_.setBody (ourBody); : message_.send (this.getEndPoint(), this.soapActionUri_, this.envelope_); 40 : try { 41 // Since the Body.unmarshall() handler is static, we can't : // replace the basic machinery easily. Instead, we must obtain 42 // and parse the message on our own. : this.soapMessage_ = this.message_.receive(); 43 XMLReader reader = : (XMLReader)Class.forName("org.apache.xerces.parsers.SAXParser").newInstance(); 44 SAXHandler handler = new SAXHandler(); : handler.setElementToSearchFor ("generateOIDResult"); 45 : // Set the Content Handler 46 reader.setContentHandler (handler); : 47 // Parse the file : reader.parse ( new InputSource (new StringReader 48 (this.soapMessage_.getContent().toString() ))); : 49 // If we reached here, the result has been parsed and : // stored in the handler instance.
50 returnValue = handler.getResult (); : } 51 catch (Exception exception) { : exception.printStackTrace (); 52 } : return returnValue; 53 } : 54 } : 55 : 56 : 57 : 58 : 59 : 60 : 61 : 62 : 63 : 64 : 65 : 66 : 67 : 68 : 69 : 70 : 71 : 72 : 73 : 74 : 75 : 76 : 77
: 78 : 79 : 80 : 81 : 82 : 83 : 84 : 85 : 86 : 87 : 88 : 89 : 90 : 91 : 92 : 93 : 94 : 95 : 96 :
Client.java
1: 2: 3: 4: 5: 6: 7: ////////////////////////////////////////////////////// /// The following example illustrates a Client to a /// WebService developed using C# and the .NET Framework. /// /// author: Gopalan Suresh Raj /// Copyright (c), 2002. All Rights Reserved. /// URL: http://gsraj.tripod.com/
8: 9: 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 : 24 : 25 : 26 : 27 : 28 : 29 : 30 : 31 : 32 : 33 : 34 : 35 : 36
/// email: gopalan@gmx.net ////////////////////////////////////////////////////// import java.io.*; /** * Web Service Client Class * @author Gopalan Suresh Raj */ public class Client { /** Entry Point to this Application */ public static void main(String[] args) { try { // Create a proxy ApacheSoapProxy proxy = new ApacheSoapProxy (); // Invoke generateOID() over SOAP and get the new OID String result = proxy.generateOID (); // Print out the value System.out.println ("The new OID is :"+result); } catch (java.net.MalformedURLException exception) { exception.printStackTrace (); } catch (org.apache.soap.SOAPException exception) { exception.printStackTrace (); } } }
: 37 : 38 :
Top of Form
node/42838
Rate form-f0f438d29b
fivestar_form_no
Bottom of Form
MARCH 4, 2003 12:00 AM EST READS:
10,295
Many articles have claimed that Java 2, Enterprise Edition (J2EE) and .NET Web services are interoperable. What does this mean from a developer's perspective? What issues and problems arise as you actually do the work? For our purposes, I'll define Web services as self-contained, modular business applications that have open, Internet-oriented, standards-based interfaces. The standards I'm referring to are Simple Object Access Protocol (SOAP) and Web Services Definition Language (WSDL). SOAP is a lightweight XMLbased message format for communication between Web services. WSDL is a schema that describes the way in which a Web service is to be used. WSDL documents answer the following questions about a Web service: What data is exchanged, how do you interact with the service, and where is the service located? When I refer to term interoperability between Web services, I mean that you can call J2EE Web services from a .NET client application and the reverse is also true. Analysts have projected that J2EE and .NET will each have approximately 40% of the Web services that will be implemented on their platform. This means that for a customer to be able to utilize all the Web services that will be available in the future, the platforms need to interoperate. Companies from both camps are working together to ensure interoperability. Several standards bodies have been formed to drive interoperability between the various Web services platform providers. For example, both SOAPBuilders and the Web Services Interoperability Organization (WS-I) are involved in interoperability efforts. To prove that simple J2EE and .NET Web services can interoperate, two things must be demonstrated. One, a .NET Web services client should be able to call and receive a response from a J2EE Web service, and two, a J2EE Web services client should be able to call and receive a response from a .NET Web service.
Case Study
In the interest of reusing code and knowledge, the same application was used to prove both points. I started with an existing J2EE application that uses servlets, JavaServerPages (JSPs), and Enterprise JavaBeans (EJBs) that I converted into a Web service. This application allows a field agent to view, edit, and create orders for a fictional construction company. The field agent is also able to sort existing orders by customer name,employee id, or order status.
a LinkedList that is not serializable. There are two possible solutions to this problem. I could choose to either change the code in the EJB to return a serializable data type and redeploy the EJB, or I could convert the data type inside the Web service before returning it to the client. I chose the second option for simplicity: I didn't have to redeploy the EJBs and I could still use the original front-end Web application if necessary. Since LinkedLists are not serializable, I converted the Web service method to return an array of "ordermasterfields". This class, ordermasterfields, is user-defined and will be included in the WSDL document so the client knows what the user-defined types are. The getArray method performs the conversion. The next step is to generate a WSDL document for use by a .NET client. In WebLogic Workshop I simply clicked on the "Generate WSDL Document" button and it automatically generated the WSDL document for me. I'm now ready to create a client that will use the Orders J2EE Web service. Notice that all we've really done is add a new Web service interface to the EJBs. As a matter of fact, the Web application front end that I started with is still usable since the EJBs have not been changed. In addition to returning HTML from the original Web application, we are now returning an XML document that contains an array of Orders via SOAP from our Web service.
WebReference1.Orders orders = new WebReference1.Orders(); WebReference1.ordermasterfields[] ordersResult = orders.getOrdersByID(userName); .NET has a concept of strict separation between the presentation and the implementation of code. They use what they call a code-behind file that is tied to the WebForm page that contains all the code (see Figure 2). All of the presentation is handled in the WebForm.aspx file, which is an ASP.NET page, and all code is written in the WebForm.aspx.cs file. The idea is the same as a JSP file where HTML is the presentation or static content and dynamic content is inserted in JSP tag sections. .NET simply extended that and moved the embedded code sections to a separate code file. I could still implement the ASP.NET page with combined HTML and ASP.NET code in the same file if I wanted to. I also created new presentation code with ASP.NET pages that duplicates what was already available on the J2EE platform, but this is not covered in this article. Request and Response Code Now that I have both a client and a Web service, let's look at what happens when I make a request to the Web service. With Web services, SOAP request and response messages are sent back and forth either asynchronously or synchronously. Here I show only the portion of the SOAP message that pertains to the Web service method I am invoking. I am calling the getOrdersByID method and passing it an ID parameter with a value of JANE.
WSDL describes the interfaces to Web service endpoints using portType definitions; each portType may have one or more operation elements. WSDL portType and operation elements are analogous to Java interfaces and methods, respectively. In fact, JAX-RPC defines a mapping between WSDL and Java that generates remote interfaces from ports, with methods that correspond to port operations. For example, a WSDL document might describe a port called BookQuote with a single operation called getBookPrice as shown in Listing 9-4.
Listing 9-4 WSDL portType and message Definitions <message name="getBookPrice"> <part name="string" type="xsd:string"/> </message> <message name="getBookPriceResponse"> <part name="result" type="xsd:float"/> </message> <portType name="BookQuote"> <operation name="getBookPrice"> <input name="isbn" message="mh:getBookPrice"/> <output message="mh:getBookPriceResponse"/> </operation> </portType>
At deployment time a JAX-RPC compiler converts the WSDL portType into a corresponding remote interfacethe endpoint interface. The JAX-RPC compiler also generates a stub that implements the endpoint interface according to the port and binding definitions. It can also create a factory for accessing the stub that conforms to the WSDL service definition. The endpoint and service interfaces would look like those in Listings 9-5 and 9-6.
Listing 9-5 An Endpoint Interface Generated by the JAX-RPC Compiler public interface BookQuote extends java.rmi.Remote { public float getBookPrice(String isbn) throws java.rmi.RemoteException; } Listing 9-6 A Service Interface Generated by the JAX-RPC Compiler public interface BookQuoteService extends javax.xml.rpc.Service{ public BookQuote getBookQuotePort( ) throws java.rmi.RemoteException; }
Once the endpoint interface and stub have been generated, they can be used at runtime to invoke operations on the Web service endpoint. The code fragment shown in Listing 9-7 demonstrates how a J2EE
component such as a servlet, application client, or EJB uses a JAX-RPC generated stub to get the wholesale price of a book from a .NET Web service.
Listing 9-7 Using a Generated Stub in a J2EE Component InitialContext jndiContext = new InitialContext ( ); BookQuoteService service = (BookQuoteService) jndiContext.lookup("java:comp/env/service/BookQuoteService"); BookQuote bookQuote = service.getBookQuotePort(); float price = bookQuote.getBookPrice( isbn );
When the getBookPrice() method is invoked, the JAX-RPC stub sends a SOAP message to the Web service endpoint, in this case a .NET program. The .NET endpoint then processes the SOAP message and sends a response back to the stub. The stub extracts the result from the SOAP message and returns it to the client (see Figure 9-3).
The generated stubs can include methods with primitive argument types like int and long, primitive wrappers like java.lang.Integer and java.lang.Long, arrays, a few standard Java types such as String and Date, custom object types, and special holder types. Holder types allow the JAX-RPC stubs to model INOUT and OUT parameter modes. Generated stubs are addressed in detail in Section 12.1.
Listing 9-8 Using a Dynamic Proxy in a J2EE Component InitialContext jndiContext = new InitialContext ( ); BookQuoteService service = (BookQuoteService) jndiContext.lookup("java:comp/env/service/DynamicService"); BookQuote BookQuote_port = (BookQuote)service.getPort(BookQuote.class); float price = BookQuote_port.getBookPrice( isbn );
At runtime the getPort() method automatically maps the BookQuote interface to a corresponding port definition in the WSDL document, then generates a stub for the interface that implements the protocol defined by the associated binding. Dynamic proxies are covered in Section 12.2: Dynamic Proxies.
9.2.3 DII
JAX-RPC also provides another, even more dynamic API, called the Dynamic Invocation Interface. DII allows the developer to assemble SOAP method calls dynamically at runtime. If you have used the CORBA Dynamic Invocation Interface, then JAX-RPC DII will be a familiar concept. JAX-RPC DII is kind of like Java Reflection. It enables you to get a reference to an object that represents a Web service operation, in the form of a method, and to invoke that method without having to use a stub or a remote interface. For example, the following fragment of code demonstrates how a J2EE component uses JAX-RPC DII to get the wholesale price of a book from some Web service.
InitialContext jndiContext = new InitialContext ( ); javax.xml.rpc.Service service = (javax.xml.rpc.Service) jndiContext.lookup("java:comp/env/service/DynamicService"); QName port = new QName("http://www.xyz.com/BookQuote ","BookQuote"); QName operation = new QName("http://www.xyz.com/BookQuote","getBookPrice"); Call callObject = service.createCall(port, operation); Object [] parameters = new Object[1]; parameters[0] = isbn; Float price = (Float) callObject.invoke( parameters );
You can actually configureat runtimethe parameters, invocation style, encoding, and so on. Everything you can configure statically with WSDL you can configure dynamically with DII. DII is covered in detail in Section 12.3. While it is certainly useful for self-organizing systems and IDEs, the majority of J2EE developers will use generated stubs for access to Web services. In most cases, you will know in advance the Web service endpoints and specific operations you will be accessing, so the DII will not be necessary.