University of Oxford |
|
Computing Services |
|
Web Services using .NET |
|
Author: Barry Cornelius Date: 6th May 2004; first created: 31st January 2002 |
Here is some background to the talk:
This talk has the following aims:
Of course, some of you may already know about .NET and Web Services.
In their book [35] about the .NET Framework, Thai and Lam provide the following answer to the question How can software be viewed as services?:
The file Service1.asmx.cs contains some code in C#. All we have to do is to add the code of each method of our Web Service to the Service1.asmx.cs file. So we could add the method:
0001: [WebMethod] 0002: public double ToFahrenheit(double pCentigrade) 0003: { 0004: return 32 + pCentigrade*9/5; 0005: }As shown above, we have to supply a WebMethod attribute for each method of the class that we wish to be accessible through the Web Service.
Such methods are declared in a class that is derived from the class WebService of the System.Web.Services namespace. It is also usual to include a WebService attribute:
0006: [WebService] 0007: public class Service1 : System.Web.Services.WebService 0008: { 0009: ... 0010: }
Note: if this class provides other methods that do not have a WebMethod attribute, they will not be (directly) accessible by an external site.
Here is an example of the code that is generated by Visual Studio.NET's wizard. I only typed in lines 24-25 and lines 61-65:
0011: using System; 0012: using System.Collections; 0013: using System.ComponentModel; 0014: using System.Data; 0015: using System.Diagnostics; 0016: using System.Web; 0017: using System.Web.Services; 0018: 0019: namespace ServerConvert 0020: { 0021: /// <summary> 0022: /// Summary description for Service1. 0023: /// </summary> 0024: [WebService(Namespace="http://www.a.com/webservices/", 0025: Description="This Web Service provides temperature conversion services.")] 0026: public class Service1 : System.Web.Services.WebService 0027: { 0028: public Service1() 0029: { 0030: //CODEGEN: This call is required by the ASP.NET Web Services Designer 0031: InitializeComponent(); 0032: } 0033: 0034: #region Component Designer generated code 0035: 0036: //Required by the Web Services Designer 0037: private IContainer components = null; 0038: 0039: /// <summary> 0040: /// Required method for Designer support - do not modify 0041: /// the contents of this method with the code editor. 0042: /// </summary> 0043: private void InitializeComponent() 0044: { 0045: } 0046: 0047: /// <summary> 0048: /// Clean up any resources being used. 0049: /// </summary> 0050: protected override void Dispose( bool disposing ) 0051: { 0052: if(disposing && components != null) 0053: { 0054: components.Dispose(); 0055: } 0056: base.Dispose(disposing); 0057: } 0058: 0059: #endregion 0060: 0061: [WebMethod(Description="This converts from Centigrade to Fahrenheit.")] 0062: public double ToFahrenheit(double pCentigrade) 0063: { 0064: return 32 + pCentigrade*9/5; 0065: } 0066: } 0067: }
The above code gives more complicated forms of the WebMethod and WebService attributes. For example, they provide information about the services that are being offered.
0068: <%@ WebService Language="c#" Codebehind="Service1.asmx.cs" 0069: Class="ServerConvert.Service1" %>
0071: <?xml version="1.0" encoding="utf-8"?> 0072: <double xmlns="http://www.a.com/webservices/"> 0073: 32 0074: </double>
C:\Program Files\Microsoft.NET\FrameworkSDK\Bin
When we are providing a client of a Web Service, all we have to do is to create an instance of the proxy class that the wizard/tool has generated, and then apply the appropriate method to that object:
0087: Service1 tService1 = new Service1(); 0088: double tFahrenheit = tService1.ToFahrenheit(tCentigrade);
So, here is a console application that calls the ToFahrenheit method:
0075: using Console = System.Console; 0076: using Service1 = ConsoleConvert.Proxy.Service1; 0077: namespace ConsoleConvert 0078: { 0079: public class Class1 0080: { 0081: public static void Main() 0082: { 0083: Console.Write("Type in a Centigrade value: "); 0084: string tCentigradeString = Console.ReadLine(); 0085: double tCentigrade = double.Parse(tCentigradeString); 0086: Console.WriteLine("Centigrade value is: " + tCentigrade); 0087: Service1 tService1 = new Service1(); 0088: double tFahrenheit = tService1.ToFahrenheit(tCentigrade); 0089: Console.WriteLine("Fahrenheit value is: " + tFahrenheit); 0090: } 0091: } 0092: }
0093: namespace ConsoleConvert.Proxy 0094: { 0095: public class Service1 : System.Web.Services.Protocols.SoapHttpClientProtocol 0096: { 0097: ... 0098: public double ToFahrenheit(double pCentigrade) 0099: { 0100: object[] results = this.Invoke("ToFahrenheit", new object[]{pCentigrade}); 0101: return (double) results[0]; 0102: } 0103: ... 0104: } 0105: }
When the ToFahrenheit method of the proxy class is called, its call of Invoke sends the appropriate HTTP request to the Web Service's site and decodes the reply that is returned. Then, an appropriate value is returned to the caller of the ToFahrenheit method.
Here is the full code of the proxy class that was generated by Visual Studio.NET:
0106: //------------------------------------------------------------------------------ 0107: // <autogenerated> 0108: // This code was generated by a tool. 0109: // Runtime Version: 1.1.4322.573 0110: // 0111: // Changes to this file may cause incorrect behavior and will be lost if 0112: // the code is regenerated. 0113: // </autogenerated> 0114: //------------------------------------------------------------------------------ 0115: 0116: // 0117: // This source code was auto-generated by Microsoft.VSDesigner, Version 1.1.4322.573. 0118: // 0119: namespace ConsoleConvert.Proxy { 0120: using System.Diagnostics; 0121: using System.Xml.Serialization; 0122: using System; 0123: using System.Web.Services.Protocols; 0124: using System.ComponentModel; 0125: using System.Web.Services; 0126: 0127: /// <remarks/> 0128: [System.Diagnostics.DebuggerStepThroughAttribute()] 0129: [System.ComponentModel.DesignerCategoryAttribute("code")] 0130: [System.Web.Services.WebServiceBindingAttribute(Name="Service1Soap", 0131: Namespace="http://www.a.com/webservices/")] 0132: public class Service1 : System.Web.Services.Protocols.SoapHttpClientProtocol { 0133: 0134: /// <remarks/> 0135: public Service1() { 0136: this.Url = 0137: "http://www.a.com/wsud/cs/ServerConvert/Service1.asmx"; 0138: } 0139: 0140: /// <remarks/> 0141: [System.Web.Services.Protocols.SoapDocumentMethodAttribute 0142: ("http://www.a.com/webservices/ToFahrenheit", 0143: RequestNamespace="http://www.a.com/webservices/", 0144: ResponseNamespace="http://www.a.com/webservices/", 0145: Use=System.Web.Services.Description.SoapBindingUse.Literal, 0146: ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] 0147: public System.Double ToFahrenheit(System.Double pCentigrade) { 0148: object[] results = this.Invoke("ToFahrenheit", new object[] { 0149: pCentigrade}); 0150: return ((System.Double)(results[0])); 0151: } 0152: 0153: /// <remarks/> 0154: public System.IAsyncResult BeginToFahrenheit(System.Double pCentigrade, 0155: System.AsyncCallback callback, object asyncState) { 0156: return this.BeginInvoke("ToFahrenheit", new object[] { 0157: pCentigrade}, callback, asyncState); 0158: } 0159: 0160: /// <remarks/> 0161: public System.Double EndToFahrenheit(System.IAsyncResult asyncResult) { 0162: object[] results = this.EndInvoke(asyncResult); 0163: return ((System.Double)(results[0])); 0164: } 0165: } 0166: }
You will see that the proxy class provides two other methods besides ToFahrenheit. If a client uses the proxy class's ToFahrenheit method, it will sit there waiting for the Web Service's webserver to reply. If, instead, the client uses the BeginToFahrenheit method of the proxy class, the call of BeginToFahrenheit will return almost immediately. In the client, the call of BeginToFahrenheit has to supply (in an argument) the method that it wants to be called when the webserver delivers its reply. That method will need to call EndToFahrenheit to get the result of the call of ToFahrenheit.
0167: http://www.a.com/pictures/index.htm
0168: GET /pictures/index.htm HTTP/1.1 0169: Host: www.a.com 0170: Content-Type: text/html 0171:
0172: HTTP/1.1 200 OK 0173: Date: Mon, 29 Mar 2004 14:40:58 GMT 0174: Server: Apache/1.3.27 (Unix) PHP/4.3.2 mod_ssl/2.8.12 OpenSSL/0.9.6g 0175: Last-Modified: Mon, 29 Mar 2004 14:37:27 GMT 0176: Accept-Ranges: bytes 0177: Content-Type: text/html; charset=iso-8859-1 0178: Content-Length: 31 0179: 0180: <html> 0181: <p> 0182: hello world! 0183: </p> 0184: </html>
Although most WWW pages are produced by means of an HTTP GET request, some WWW pages are the result of an HTTP POST request. HTTP POST requests often occur when you click on the submit button of a web form. For example, suppose a WWW page provides the following web form:
0185: <form method="POST" action="http://www.a.com/pictures/query.php"> 0186: <input type="text" name="last"/> 0187: <input type="text" name="first"/> 0188: <input type="text" name="age"/> 0189: <input type="submit" value="submit/> 0190: </form>Suppose the visitor to this WWW page types some values (say Bloggs, Fred and 27) into the three textboxes and then clicks the submit button. Because the form has a method attribute of "POST", an HTTP POST request will be generated:
0191: POST /pictures/query.php HTTP/1.1 0192: Host: www.a.com 0193: Content-Type: application/x-www-form-urlencoded 0194: Content-Length: XXXX 0195: 0196: last=Bloggs&first=Fred&age=27
The .NET Framework provides three mechanisms for allowing a Web Service to be contacted:
The reply from the Web Service is sent as an HTTP reply usually with a body coded in XML.0197: POST /wsud/cs/ServerConvert/Service1.asmx HTTP/1.1 0198: Host: www.a.com 0199: Content-Type: text/xml; charset=utf-8 0200: Content-Length: XXXX 0201: SOAPAction: "http://www.a.com/webservices/ToFahrenheit" 0202: 0203: <?xml version="1.0" encoding="utf-8"?> 0204: <soap:Envelope 0205: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 0206: xmlns:xsd="http://www.w3.org/2001/XMLSchema" 0207: xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> 0208: <soap:Body> 0209: <ToFahrenheit xmlns="http://www.a.com/webservices/"> 0210: <pCentigrade>0</pCentigrade> 0211: </ToFahrenheit> 0212: </soap:Body> 0213: </soap:Envelope>
Hiding in all this detail is the fact that we want to contact a webserver on www.a.com to visit the page at /wsud/cs/ServerConvert/Service1.asmx to execute the ToFahrenheit method with an argument (pCentigrade) of 0.
The reply from the webserver (after the appropriate method has been executed) is likely to be XML coded using SOAP. An example is:
0214: HTTP/1.1 200 OK 0215: Content-Type: text/xml; charset=utf-8 0216: Content-Length: YYYY 0217: 0218: <?xml version="1.0" encoding="utf-8"?> 0219: <soap:Envelope 0220: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 0221: xmlns:xsd="http://www.w3.org/2001/XMLSchema" 0222: xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> 0223: <soap:Body> 0224: <ToFahrenheitResponse xmlns="http://www.a.com/webservices/"> 0225: <ToFahrenheitResult>32</ToFahrenheitResult> 0226: </ToFahrenheitResponse> 0227: </soap:Body> 0228: </soap:Envelope>Hidden away in all this XML is the fact that the result of the call of the method is the value 32.
0100: object[] results = this.Invoke("ToFahrenheit", new object[]{pCentigrade});
0136: this.Url = 0137: "http://www.a.com/wsud/cs/ServerConvert/Service1.asmx";to:
0229: this.Url = 0230: "http://localhost:8080/wsud/cs/ServerConvert/Service1.asmx";where 8080 is an arbitrarily chosen port.
0231: set AXIS_HOME=E:\axis-1_1RC2 0232: set CLASSPATH=. 0233: set CLASSPATH=%AXIS_HOME%\lib\axis.jar;%CLASSPATH% 0234: set CLASSPATH=%AXIS_HOME%\lib\commons-discovery.jar;%CLASSPATH% 0235: set CLASSPATH=%AXIS_HOME%\lib\commons-logging.jar;%CLASSPATH% 0236: set CLASSPATH=%AXIS_HOME%\lib\jaxrpc.jar;%CLASSPATH% 0237: set CLASSPATH=%AXIS_HOME%\lib\saaj.jar;%CLASSPATH% 0238: set CLASSPATH=%AXIS_HOME%\lib\log4j-1.2.4.jar;%CLASSPATH% 0239: set CLASSPATH=%AXIS_HOME%\lib\wsdl4j.jar;%CLASSPATH%
0240: java org.apache.axis.utils.tcpmon 8080 www.a.com 80
Various companies have been working on producing specifications for a number of areas that build on the basics of Web Services. These include:
These specifications are examined by the papers at [9, 11]. Other papers that look at the bigger picture include those at [10, 16, 29].
In the next section of this document, we just consider the first of these: WS-Security.
Although you could use https instead of http to ensure SOAP messages are encrypted when they pass from A to B, this is regarded as inadequate. One of the problems with using https is that if B decides to send the SOAP message to some other machine C, A has no control over whether https gets used by B. So, can we do something different about security without using https?
IBM, Microsoft and Verisign have released a specification called 'WS-Security' [12, 22, 37] that provides a number of ways in which SOAP messages can be transferred securely.
In this document, we will only look at the first of these: providing a username and a password.
The WS-Security specification provides three ways in which a username/password can be communicated in a UsernameToken element of the header of a SOAP message:
However, even if the password is encrypted, ‘an evil intermediary could [capture the information in a SOAP header and later] send the hashed password and then could be authenticated as the original sender’ [27].
So in a later proposal (called the 'WS-Security Addendum' [13]), ‘instead of sending just a hash of the password, the addendum specifies that a digest version of the password should be sent [containing] a combination of the password, a Nonce (functionally a unique string that identifies this request), and the creation time’ [27]. A server could use the Nonce and the timestamp values to ensure that replays do not take place.
In December 2002, Microsoft released the 'Web Services Enhancements' (WSE) [23, 25, 26, 27, 28, 29]. This includes an implementation of the WS-Security specification (and its Addendum).
In order to use the WSE, you will need to download it and install it. The latest version is available from [24].
0241: public class MyUsernameTokenManager : UsernameTokenManager 0242: { 0243: protected override string AuthenticateToken( 0244: UsernameToken pUsernameToken) 0245: { 0246: switch(pUsernameToken.Username) 0247: { 0248: case "superman": 0249: return "mansuper"; 0250: default: 0251: throw new SoapException("Unrecognised username", 0252: SoapException.ClientFaultCode); 0253: } 0254: } 0255: }
In order to use the WSE, you will need to make some changes to the web.config file that is used by the Web Service. This file is stored in the same directory as the other files of the project (e.g., Service.asmx.cs). It contains an XML document. You will need to add the following to its configuration element:
0256: <configSections> 0257: <section 0258: name="microsoft.web.services" 0259: type="Microsoft.Web.Services.Configuration.WebServicesConfiguration, 0260: Microsoft.Web.Services, Version=2.0.0.0, Culture=neutral, 0261: PublicKeyToken=31bf3856ad364e35" /> 0262: </configSections> 0263: <microsoft.web.services> 0264: <security> 0265: <securityTokenManager 0266: qname="wsse:UsernameToken" 0267: xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" 0268: type="ServerUsername.MyUsernameTokenManager, ServerUsername" /> 0269: </security> 0270: </microsoft.web.services>and the following needs to be added to the system.web element:
0271: <webServices> 0272: <soapExtensionTypes> 0273: <add 0274: type="Microsoft.Web.Services.WebServicesExtension, 0275: Microsoft.Web.Services, Version=2.0.0.0, Culture=neutral, 0276: PublicKeyToken=31bf3856ad364e35" 0277: priority="1" 0278: group="0"/> 0279: </soapExtensionTypes> 0280: </webServices>Note: the text of each of the two type elements given above needs to be typed on one line: above, they are presented on several lines in order to help you read them.
One of the changes to the web.config file arranges for it to include a securityTokenManager element. The contents of this element ensure that ServerUsername.MyUsernameTokenManager's AuthenticateToken method gets used.
Now, whenever the Web Service is contacted, automatically behind the scenes, the WSE SOAP Extension will authenticate the usernames/passwords of any UsernameToken elements of the header of the SOAP message.
But we have to ensure that an incoming SOAP message actually has one UsernameToken element. In the following code for a Web Service providing a ToFahrenheit method, this is done by a subsidiary method called iGetUsernameToken:
0281: using PasswordOption = Microsoft.Web.Services.Security.PasswordOption; 0282: using RequestSoapContext = Microsoft.Web.Services.RequestSoapContext; 0283: using SecurityToken = Microsoft.Web.Services.Security.SecurityToken; 0284: using SoapContext = Microsoft.Web.Services.SoapContext; 0285: using SoapException = System.Web.Services.Protocols.SoapException; 0286: using UsernameToken = Microsoft.Web.Services.Security.UsernameToken; 0287: using UsernameTokenManager = Microsoft.Web.Services.Security.Tokens.UsernameTokenManager; 0288: using System; 0289: using System.Collections; 0290: using System.ComponentModel; 0291: using System.Data; 0292: using System.Diagnostics; 0293: using System.Web; 0294: using System.Web.Services; 0295: namespace ServerUsername 0296: { 0297: [WebService(Namespace="http://www.a.com/webservices/", 0298: Description="This Web Service provides temperature conversion services.")] 0299: public class Service1 : System.Web.Services.WebService 0300: { 0301: ... <<< as lines 28 to 60 on page 3 of this document >>> 0302: [WebMethod(Description="This converts from Centigrade to Fahrenheit.")] 0303: public double ToFahrenheit(double pCentigrade) 0304: { 0305: UsernameToken tUsernameToken = iGetUsernameToken(); 0306: return 32 + pCentigrade*9/5; 0307: } 0308: private static UsernameToken iGetUsernameToken() 0309: { 0310: SoapContext tSoapContext = RequestSoapContext.Current; 0311: if (tSoapContext==null) 0312: { 0313: throw new Exception("Only SOAP requests are permitted"); 0314: } 0315: if (tSoapContext.Security.Tokens.Count!=1) 0316: { 0317: throw new SoapException("There must be one security token", 0318: SoapException.ClientFaultCode); 0319: } 0320: foreach (SecurityToken tSecurityToken in tSoapContext.Security.Tokens) 0321: { 0322: if (tSecurityToken is UsernameToken) 0323: { 0324: UsernameToken tUsernameToken = (UsernameToken)tSecurityToken; 0325: if (tUsernameToken.PasswordOption==PasswordOption.SendHashed) 0326: { 0327: return tUsernameToken; 0328: } 0329: else 0330: { 0331: throw new SoapException("The PasswordOption has the wrong value", 0332: SoapException.ClientFaultCode); 0333: } 0334: } 0335: else 0336: { 0337: throw new SoapException("A UsernameToken was expected", 0338: SoapException.ClientFaultCode); 0339: } 0340: } 0341: throw new SoapException("A SecurityToken has not been found", 0342: SoapException.ClientFaultCode); 0343: } 0344: } 0345: ... <<< as lines 241 to 255 on page 11 of this document >>> 0346: }
You will see that this code uses some types from the Microsoft.Web.Services namespace (and from some of its subnamespaces). These namespaces are a part of the WSE. In order to get Visual Studio.NET to find these types, you will need to go to the Add Reference option of its Project menu, and then use this option to add a reference to Microsoft.Web.Services.dll. This file is one of those that will have been created by installing the WSE.
Although the above code is checking that there is a UsernameToken element, it does not prevent replay attacks: it is being lazy in that it does not include code to check the Nonce (tUsernameToken.Nonce) and the timestamp value (tUsernameToken.Created) of the incoming SOAP message.
We also have to alter the client so that it arranges for a username and a password to be present in the header of the SOAP message that it generates.
Currently, the proxy class (Service1) is derived from the class:
System.Web.Services.Protocols.SoapHttpClientProtocolService1 needs to be altered so that it is derived from the class:
Microsoft.Web.Services.WebServicesClientProtocolThe latter class is itself derived from:
System.Web.Services.Protocols.SoapHttpClientProtocoland so Service1 can be used as before but it now has access to the other facilities that are in its new base class: these facilities are there to support WS-Security.
We can then add the username and the password to the header of the SOAP message by applying the appropriate method to the Service1 object:
0347: using Console = System.Console; 0348: using Exception = System.Exception; 0349: using PasswordOption = Microsoft.Web.Services.Security.PasswordOption; 0350: using Service1 = ConsoleUsername.Proxy.Service1; 0351: using UsernameToken = Microsoft.Web.Services.Security.UsernameToken; 0352: namespace ConsoleUsername 0353: { 0354: public class Class1 0355: { 0356: public static void Main() 0357: { 0358: Console.Write("Type in a Centigrade value: "); 0359: string tCentigradeString = Console.ReadLine(); 0360: double tCentigrade = double.Parse(tCentigradeString); 0361: Console.WriteLine("Centigrade value is: " + tCentigrade); 0362: Console.Write("Type in a Username: "); 0363: string tUsername = Console.ReadLine(); 0364: Console.WriteLine("Username is: " + tUsername); 0365: Console.Write("Type in a Password: "); 0366: string tPassword = Console.ReadLine(); 0367: Console.WriteLine("Password is: " + tPassword); 0368: UsernameToken tUsernameToken = 0369: new UsernameToken(tUsername, tPassword, PasswordOption.SendHashed); 0370: Service1 tService1 = new Service1(); 0371: tService1.RequestSoapContext.Security.Tokens.Add(tUsernameToken); 0372: try 0373: { 0374: double tFahrenheit = tService1.ToFahrenheit(tCentigrade); 0375: Console.WriteLine("Fahrenheit value is: " + tFahrenheit); 0376: } 0377: catch(Exception pException) 0378: { 0379: Console.WriteLine(pException.Message); 0380: } 0381: } 0382: } 0383: }
Once again, the above code uses types from a subnamespace of the Microsoft.Web.Services namespace. As before, in order to get Visual Studio.NET to find these types, you will need to go to the Add Reference option of its Project menu, and then use this to add a reference to Microsoft.Web.Services.dll.
A complete WSDL file is given below. Here is an explanation of some parts of the file:
Here is a WSDL file that could be used to describe the Web Service being offered by Service1.asmx:
0384: <?xml version="1.0" encoding="utf-8"?> 0385: <definitions 0386: xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" 0387: xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 0388: xmlns:s="http://www.w3.org/2001/XMLSchema" 0389: xmlns:s0="http://www.a.com/webservices/" 0390: xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 0391: xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" 0392: xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" 0393: targetNamespace="http://www.a.com/webservices/" 0394: xmlns="http://schemas.xmlsoap.org/wsdl/"> 0395: <types> 0396: <s:schema 0397: elementFormDefault="qualified" 0398: targetNamespace="http://www.a.com/webservices/"> 0399: <s:element name="ToFahrenheit"> 0400: <s:complexType> 0401: <s:sequence> 0402: <s:element minOccurs="1" maxOccurs="1" name="pCentigrade" 0403: type="s:double" /> 0404: </s:sequence> 0405: </s:complexType> 0406: </s:element> 0407: <s:element name="ToFahrenheitResponse"> 0408: <s:complexType> 0409: <s:sequence> 0410: <s:element minOccurs="1" maxOccurs="1" name="ToFahrenheitResult" 0411: type="s:double" /> 0412: </s:sequence> 0413: </s:complexType> 0414: </s:element> 0415: </s:schema> 0416: </types> 0417: <message name="ToFahrenheitSoapIn"> 0418: <part name="parameters" element="s0:ToFahrenheit" /> 0419: </message> 0420: <message name="ToFahrenheitSoapOut"> 0421: <part name="parameters" element="s0:ToFahrenheitResponse" /> 0422: </message> 0423: <portType name="Service1Soap"> 0424: <operation name="ToFahrenheit"> 0425: <documentation>This converts from Centigrade to Fahrenheit.</documentation> 0426: <input message="s0:ToFahrenheitSoapIn" /> 0427: <output message="s0:ToFahrenheitSoapOut" /> 0428: </operation> 0429: </portType> 0430: <binding name="Service1Soap" type="s0:Service1Soap"> 0431: <soap:binding transport="http://schemas.xmlsoap.org/soap/http" 0432: style="document" /> 0433: <operation name="ToFahrenheit"> 0434: <soap:operation 0435: soapAction="http://www.a.com/webservices/ToFahrenheit" 0436: style="document" /> 0437: <input> 0438: <soap:body use="literal" /> 0439: </input> 0440: <output> 0441: <soap:body use="literal" /> 0442: </output> 0443: </operation> 0444: </binding> 0445: <service name="Service1"> 0446: <documentation>This Web Service provides temperature conversion 0447: services.</documentation> 0448: <port name="Service1Soap" binding="s0:Service1Soap"> 0449: <soap:address 0450: location="http://www.a.com/wsud/cs/ServerConvert/Service1.asmx" /> 0451: </port> 0452: </service> 0453: </definitions>
0454: http://www.a.com/wsud/cs/ServerConvert/Service1.asmx?wsdl
As mentioned earlier, the .NET Framework is not the only way of providing a Web Service or accessing a Web Service. There are many other products that can be used. Here are two examples:
I have successfully installed each of the above products; used them to create Web Services; and used them to access Web Services. I have also successfully interoperated between the above products and .NET.
The next section of this document looks at how Axis can be used to access the .NET Web Service that was developed earlier in this document.
0455: java org.apache.axis.wsdl.WSDL2Java Service1.wsdl
Service1.java Service1Locator.java Service1Soap.java Service1SoapStub.javain the subdirectory com\a\www\webservices.
These interfaces and classes are used in the following Java program:
0456: import java.rmi. RemoteException; // ConsoleClient.java 0457: import javax.xml.rpc. ServiceException; 0458: import com.a.www.webservices. Service1; 0459: import com.a.www.webservices. Service1Soap; 0460: import com.a.www.webservices. Service1Locator; 0461: public class ConsoleClient 0462: { 0463: public static void main(String[] pArgs) 0464: throws RemoteException, ServiceException 0465: { 0466: double tCentigrade = Double.parseDouble(pArgs[0]); 0467: System.out.println("Centigrade: " + tCentigrade); 0468: Service1 tService1 = new Service1Locator(); 0469: Service1Soap tService1Soap = tService1.getService1Soap(); 0470: double tFahrenheit = tService1Soap.toFahrenheit(tCentigrade); 0471: System.out.println("Fahrenheit: " + tFahrenheit); 0472: } 0473: }
0474: javac ConsoleClient.java 0475: java ConsoleClient 0.0
0470: double tFahrenheit = tService1Soap.toFahrenheit(tCentigrade);
Amazon provide a number of web sites around the world (including www.amazon.co.uk). Many of these Amazon sites provide a Web Service.
There is information about Amazon Web Services at:
http://www.amazon.com/webservices/
The WSDL document for accessing the Web Service provided by
the Amazon sites in UK and Germany is located at:
http://soap-eu.amazon.com/schemas3/AmazonWebServices.wsdl
and the WSDL document for the Amazon Web Services
at its
USA and Japan sites is located at:
http://soap.amazon.com/schemas3/AmazonWebServices.wsdl
We can use one of the above WSDL documents to generate a proxy class for accessing the information stored at an Amazon site. You will get a proxy class called AmazonSearchService together with a number of supporting classes such as AsinRequest. The latter is used to generate a search for a particular product.
The following console application accesses the information stored at the www.amazon.co.uk site. An affliate of Amazon's site should set the tag field (of an AsinRequest object) to their affliate name. If you are not an Amazon affliate, you should set this field to "webservices-20". Whether or not you are an affliate, you still have to register with Amazon to use their Web Service. In the following code, my registration id has been replaced by "XXXXXXXXXXXXXX". Besides using the WSDL document that is appropriate for the particular Amazon site you wish to access, it is also necessary to set the locale field. In the following code, this field is set to uk. If your query only requires access to a small number of the available fields, you can set the type field to "lite": if instead you want all of the values, set this field to "heavy".
It seems it is part of the design of Amazon's Web Service to return null if a value is not available in time: it seems that their view is that it is better to be fast in providing some of the information rather than hanging around for all of it to become available.
0476: using AmazonSearchService = ConsoleAmazon.Proxy.AmazonSearchService; 0477: using AsinRequest = ConsoleAmazon.Proxy.AsinRequest; 0478: using Console = System.Console; 0479: using Details = ConsoleAmazon.Proxy.Details; 0480: using ProductInfo = ConsoleAmazon.Proxy.ProductInfo; 0481: namespace ConsoleAmazon 0482: { 0483: public class Class1 0484: { 0485: public static void Main() 0486: { 0487: Console.Write("Type in an ISBN: "); 0488: string tIsbnString = Console.ReadLine(); 0489: AmazonSearchService tAmazonSearchService = 0490: new AmazonSearchService(); 0491: AsinRequest tAsinRequest = new AsinRequest(); 0492: tAsinRequest.tag = "webservices-20"; 0493: tAsinRequest.devtag = "XXXXXXXXXXXXXX"; 0494: tAsinRequest.asin = tIsbnString; 0495: tAsinRequest.locale = "uk"; 0496: tAsinRequest.type = "heavy"; 0497: ProductInfo tProductInfo = 0498: tAmazonSearchService.AsinSearchRequest(tAsinRequest); 0499: Details[] tDetailsArray = tProductInfo.Details; 0500: Details tDetails = tDetailsArray[0]; 0501: Console.WriteLine(tDetails.ProductName); 0502: Console.WriteLine(tDetails.ReleaseDate); 0503: Console.WriteLine(tDetails.OurPrice); 0504: Console.WriteLine(tDetails.ListPrice); 0505: Console.WriteLine(tDetails.UsedPrice); 0506: Console.WriteLine(tDetails.SalesRank); 0507: Console.WriteLine(tDetails.Availability); 0508: Console.WriteLine(tDetails.ImageUrlSmall); 0509: Console.WriteLine(tDetails.ImageUrlMedium); 0510: Console.WriteLine(tDetails.ImageUrlLarge); 0511: } 0512: } 0513: }
0514: Publisher: dpchiesa 0515: Service Name: ZipToCityState 0516: Description: Retrieves valid City+State pairs for a given 0517: US Zip Code, or longitude/latitude for a zipcode. 0518: Also retrieves zipcodes for city/state pairs 0519: Implementation: MS .NET
http://www.winisp.net/cheeso/zips/ZipService.asmx?WSDL
The above URL ends in .asmx?WSDL.
The .asmx is a sure sign that this Web Service
has been implemented using Microsoft's .NET Framework.
As explained earlier,
one of the benefits of using .NET for Web Services
is that WWW pages that can be used
to test a Web Service are dynamically generated.
You can go to the first of these WWW pages
by using the above URL without the last 5 characters,
i.e., by going to:
http://www.winisp.net/cheeso/zips/ZipService.asmx
You should get a WWW page saying:
0520: The following operations are supported. For a formal definition, 0521: please review the Service Description. 0522: * ZipToLatLong 0523: * CityToZip 0524: * ZipToCityAndState 0525: * CityTo1Zip 0526: * ZipTo1CityAndState
This
is a list of the names of the methods provided by the Web Service.
If you now click on one of these names, e.g.,
ZipTo1CityAndState,
another WWW page is dynamically generated.
It is the WWW page at:
http://www.winisp.net/cheeso/zips/ZipService.asmx?op=ZipTo1CityAndState
This WWW page provides a textbox
and an Invoke button.
If you type a zipcode like 94042 into the textbox and click on the Invoke button, the browser will contact the webserver at www.winisp.net to get it to execute the ZipTo1CityAndState method. After a little time, your browser will give you the following WWW page:
0527: <?xml version="1.0" encoding="utf-8"?> 0528: <string xmlns="http://dinoch.dyndns.org/webservices/"> 0529: MOUNTAIN VIEW CA 0530: </string>
You may have to click on View Page Source to see this XML.
You could use the techniques described in Section 4 of this document to provide a program that accesses this Web Service.