[ Team LiB ] |
10.2 Using Web ServicesUsing Web Services can be broken down into five distinct steps: choosing and implementing the Web Services provider, describing the web service, handling web service requests, creating web service clients, and publishing the web service. 10.2.1 Choosing a Web Services ProviderBefore you begin developing your web service, you need to decide how you're going host it. You have several choices: ASP.NET and .NET Remoting are the easiest ones to choose, and I'll be focusing on ASP.NET in these examples, because it's the option that gives you the most flexibility. If you choose to serve your web services with ASP.NET, you need to be sure you have a web server capable of serving ASP.NET pages. IIS, the web server that ships with all Windows NT and Windows Server installations, will do just fine. However, if you're running on Windows XP personal workstation, you don't have a web server.Describing Web Services
Figure 10-1. Cassini start screenFigure 10-2. Cassini Web Server settingsA web service is described with a WSDL file. The following elements are involved in a WSDL document:
Now I'll build a relatively simple WSDL document, which describes an inventory query service which I'll introduce a little later. The XML prolog and document element are fairly uneventful, except for the large number of namespaces. The namespaces will be used for various purposes later in the document: <?xml version="1.0" encoding="utf-8"?> <definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:s0="http://angushardware.com" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" targetNamespace="http://angushardware.com" xmlns="http://schemas.xmlsoap.org/wsdl/"> The types element defines three elements using XML Schema: GetNumberInStock, GetNumberInStockResponse, and int. These elements will all be scoped in the target namespace, http://angushardware.com. The first two are complex types which define the parameters and return values of the messages, and the last one is equivalent to the predefined xs:int type: <types> <s:schema elementFormDefault="qualified" targetNamespace="http://angushardware.com"> <s:element name="GetNumberInStock"> <s:complexType> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="productCode" type="s:string" /> </s:sequence> </s:complexType> </s:element> <s:element name="GetNumberInStockResponse"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="GetNumberInStockResult" type="s:int" /> </s:sequence> </s:complexType> </s:element> <s:element name="int" type="s:int" /> </s:schema> </types> The two messages are defined here. GetNumberInStockSoapIn is a SOAP version of the GetNumberinStock request message, and GetNumberInStockSoapOut is a SOAP version of the GetNumberInStockResponse response message: <message name="GetNumberInStockSoapIn"> <part name="parameters" element="s0:GetNumberInStock" /> </message> <message name="GetNumberInStockSoapOut"> <part name="parameters" element="s0:GetNumberInStockResponse" /> </message> This web service only supports a single operation, GetNumberInStock, so there is only one portType element. This element maps the GetNumberInStock operation to its SOAP input and output messages: <portType name="InventoryQuerySoap"> <operation name="GetNumberInStock"> <input message="s0:GetNumberInStockSoapIn" /> <output message="s0:GetNumberInStockSoapOut" /> </operation> </portType> The binding element associates the InventoryQuerySoap portType with the SOAP transport, and defines the GetNumberInStock operation as a SOAP message: <binding name="InventoryQuerySoap" type="s0:InventoryQuerySoap"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <operation name="GetNumberInStock"> <soap:operation soapAction="http://angushardware.com/GetNumberInStock" style="document" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> </binding> The service element describes the InventoryQuery service as being located at the URL http://127.0.0.1/dotNetAndXml/InventoryQuery.asmx, using the InventoryQuerySoap binding: <service name="InventoryQuery"> <port name="InventoryQuerySoap" binding="s0:InventoryQuerySoap"> <soap:address location="http://127.0.0.1/dotNetAndXml/InventoryQuery.asmx" /> </port> </service> Finally, as in all XML documents, the root element has to be closed: </definitions> That's it, the InventoryQuery web service is now fully described. Example 10-1 shows the complete WSDL document I built. It's not a very complicated schema, but its contents can be confusing. Don't worry, though; you'll very rarely have to create it by hand. You'll see in a moment how the .NET Framework creates one for you on demand. Example 10-1. WSDL document for InventoryQuery service<?xml version="1.0" encoding="utf-8"?> <definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:s0="http://angushardware.com" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" targetNamespace="http://angushardware.com" xmlns="http://schemas.xmlsoap.org/wsdl/"> <types> <s:schema elementFormDefault="qualified" targetNamespace="http://angushardware.com"> <s:element name="GetNumberInStock"> <s:complexType> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="productCode" type="s:string" /> </s:sequence> </s:complexType> </s:element> <s:element name="GetNumberInStockResponse"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="GetNumberInStockResult" type="s:int" /> </s:sequence> </s:complexType> </s:element> <s:element name="int" type="s:int" /> </s:schema> </types> <message name="GetNumberInStockSoapIn"> <part name="parameters" element="s0:GetNumberInStock" /> </message> <message name="GetNumberInStockSoapOut"> <part name="parameters" element="s0:GetNumberInStockResponse" /> </message> <portType name="InventoryQuerySoap"> <operation name="GetNumberInStock"> <input message="s0:GetNumberInStockSoapIn" /> <output message="s0:GetNumberInStockSoapOut" /> </operation> </portType> <binding name="InventoryQuerySoap" type="s0:InventoryQuerySoap"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <operation name="GetNumberInStock"> <soap:operation soapAction="http://angushardware.com/GetNumberInStock" style="document" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> </binding> <service name="InventoryQuery"> <port name="InventoryQuerySoap" binding="s0:InventoryQuerySoap"> <soap:address location="http://127.0.0.1/dotNetAndXml/InventoryQuery.asmx" /> </port> </service> </definitions>
10.2.2 Creating a Web ServiceAt its simplest, creating a web service in .NET can be almost trivially easy. I'm going to start with a simple inventory query service. Example 10-2 shows the basic ASP.NET skeleton for such a service. Example 10-2. InventoryQuery.asmx source code<%@ WebService Language="C#" Class="InventoryQuery" %> using System.Web.Services; [WebService(Namespace="http://angushardware.com/InventoryQuery")] public class InventoryQuery : WebService { [WebMethod] public int GetNumberInStock(string productCode) { return 0; } } Let's break this skeleton down into its basic components. The presence of the @ WebService directive in a file with the .asmx extension tells the ASP.NET provider that the web service is located at InventoryQuery.asmx, that the web service's source code is written in C#, and that the implementation is in the class named InventoryQuery. The code could also be written in JScript .NET (JS) or Visual Basic .NET (VB). Additionally, the code could actually reside in a separate file, compiled into an assembly located in the .\Bin directory relative to the .asmx file: <%@ WebService Language="C#" Class="InventoryQuery" %>
The WebService attribute comes from the System.Web.Services namespace, and indicates that the class in question represents the implementation of a web service. The Namespace property sets the default namespace for the web service. The WebService attribute also has Name and Description properties, which allow you to set the public name of the web service, and give it a short textual description. The Name property defaults to the name of the class. A class that implements a web service does not actually need to have the WebService attribute; any class can implement a web service: using System.Web.Services; [WebService(Namespace="http://angushardware.com/")]
Web service implementations can extend the WebService type. The WebService type provides access to state information through its Application, Context, Server, Session, and User properties. Although extending WebService is not required for a web service implementation, I have chosen to do so in this example: public class InventoryQuery : WebService {
Finally, the GetNumberInStock method represents the InventoryQuery web service's GetNumberInStock message itself. Right now it will always return 0, since I've only created a stub method. The WebMethod attribute indicates that the method it is attached to implements a particular web service message. By default, the name of the message is the name of the method itself, although the WebMethod attribute has a MessageName property that allows you to override the name. WebMethod also has an optional Description property: [WebMethod] public int GetNumberInStock(string productCode) { return 0; }
To see the InventoryQuery web service in action, make sure the InventoryQuery.asmx file is in C:\dotNetAndXml\ (or whatever directory you set as the application directory in your web server), and navigate your web browser to http://localhost/dotNetAndXml/InventoryQuery.asmx. You should see the page in Figure 10-3. Figure 10-3. Main screen of the InventoryQuery web serviceThis HTML page is generated by the ASP.NET provider, based on the metadata included in the .asmx file and the class that implements the web service. If either the WebService attribute or the WebMethod attribute included a Description property, the descriptive text would be displayed here as well. If any more methods were exposed by attaching the WebMethod attribute to them, they would all be listed on this page as well. Clicking on the "Service Description" link opens a new window containing the WSDL file that the ASP.NET provider has automatically generated. Example 10-3 shows the generated WSDL for the InventoryQuery web service. Note the similarities to Example 10-1. Example 10-3. Generated WSDL for the InventoryQuery web service<?xml version="1.0" encoding="utf-8"?> <definitions xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:s0="http://angushardware.com" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" targetNamespace="http://angushardware.com" xmlns="http://schemas.xmlsoap.org/wsdl/"> <types> <s:schema elementFormDefault="qualified" targetNamespace="http://angushardware.com"> <s:element name="GetNumberInStock"> <s:complexType> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="productCode" type="s:string" /> </s:sequence> </s:complexType> </s:element> <s:element name="GetNumberInStockResponse"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="GetNumberInStockResult" type="s:int" /> </s:sequence> </s:complexType> </s:element> <s:element name="int" type="s:int" /> </s:schema> </types> <message name="GetNumberInStockSoapIn"> <part name="parameters" element="s0:GetNumberInStock" /> </message> <message name="GetNumberInStockSoapOut"> <part name="parameters" element="s0:GetNumberInStockResponse" /> </message> <message name="GetNumberInStockHttpGetIn"> <part name="productCode" type="s:string" /> </message> <message name="GetNumberInStockHttpGetOut"> <part name="Body" element="s0:int" /> </message> <message name="GetNumberInStockHttpPostIn"> <part name="productCode" type="s:string" /> </message> <message name="GetNumberInStockHttpPostOut"> <part name="Body" element="s0:int" /> </message> <portType name="InventoryQuerySoap"> <operation name="GetNumberInStock"> <input message="s0:GetNumberInStockSoapIn" /> <output message="s0:GetNumberInStockSoapOut" /> </operation> </portType> <portType name="InventoryQueryHttpGet"> <operation name="GetNumberInStock"> <input message="s0:GetNumberInStockHttpGetIn" /> <output message="s0:GetNumberInStockHttpGetOut" /> </operation> </portType> <portType name="InventoryQueryHttpPost"> <operation name="GetNumberInStock"> <input message="s0:GetNumberInStockHttpPostIn" /> <output message="s0:GetNumberInStockHttpPostOut" /> </operation> </portType> <binding name="InventoryQuerySoap" type="s0:InventoryQuerySoap"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <operation name="GetNumberInStock"> <soap:operation soapAction="http://angushardware.com/GetNumberInStock" style="document" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> </binding> <binding name="InventoryQueryHttpGet" type="s0:InventoryQueryHttpGet"> <http:binding verb="GET" /> <operation name="GetNumberInStock"> <http:operation location="/GetNumberInStock" /> <input> <http:urlEncoded /> </input> <output> <mime:mimeXml part="Body" /> </output> </operation> </binding> <binding name="InventoryQueryHttpPost" type="s0:InventoryQueryHttpPost"> <http:binding verb="POST" /> <operation name="GetNumberInStock"> <http:operation location="/GetNumberInStock" /> <input> <mime:content type="application/x-www-form-urlencoded" /> </input> <output> <mime:mimeXml part="Body" /> </output> </operation> </binding> <service name="InventoryQuery"> <port name="InventoryQuerySoap" binding="s0:InventoryQuerySoap"> <soap:address location="http://127.0.0.1/dotNetAndXml/InventoryQuery.asmx" /> </port> <port name="InventoryQueryHttpGet" binding="s0:InventoryQueryHttpGet"> <http:address location="http://127.0.0.1/dotNetAndXml/InventoryQuery.asmx" /> </port> <port name="InventoryQueryHttpPost" binding="s0:InventoryQueryHttpPost"> <http:address location="http://127.0.0.1/dotNetAndXml/InventoryQuery.asmx" /> </port> </service> </definitions> As you'll recall from the earlier discussion, the WSDL document provides a complete description of the web service, including all the supported messages, types, port types, bindings, and services. In this case, the ASP.NET provider automatically supports REST-style HTTP POST and GET methods as well as SOAP over HTTP POST.
The generated WSDL file in Example 10-3 contains more information than the one in Example 10-1. However, you can see that the only real difference is the inclusion of additional transports for HTTP GET and HTTP POST. The .NET Web Services provider creates these bindings, in addition to SOAP, automatically. Clicking on the "GetNumberInStock" link in Figure 10-3 will bring you to the page shown in Figure 10-4. This HTML page is also generated automatically by the ASP.NET Web Services provider. Figure 10-4. GetNumberInStock test pageFrom this page, you can issue a request to the GetNumberInStock method of the InventoryQuery web service. Entering in a value—say, "803B"—and clicking the Invoke button causes the method to be invoked with the given parameter. This example uses the HTTP GET version of the web service, so the request that was actually sent to the web service provider used the following URL: http://127.0.0.1/dotNetAndXml/InventoryQuery.asmx/GetNumberInStock?productCode=803B. Because right now the C# code always returns 0, the following response is always returned: <?xml version="1.0" encoding="utf-8" ?> <int xmlns="http://angushardware.com">0</int> This would also be returned from the HTTP POST version. The SOAP request, however, would look quite a bit different. It would be sent with the following HTTP header and SOAP request envelope: POST /dotNetAndXml/InventoryQuery.asmx HTTP/1.1 Host: 127.0.0.1 Content-Type: text/xml; charset=utf-8 Content-Length: 365 SOAPAction: "http://angushardware.com/GetNumberInStock" <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetNumberInStock xmlns="http://angushardware.com"> <productCode>803B</productCode> </GetNumberInStock> </soap:Body> </soap:Envelope> The HTTP response header and SOAP response envelope would be the following: HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: 400 <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=" http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetNumberInStockResponse xmlns="http://angushardware.com"> <GetNumberInStockResult>0</GetNumberInStockResult> </GetNumberInStockResponse> </soap:Body> </soap:Envelope> If you scroll a little further down the page in Figure 10-4, you'll see examples of requests and responses in all three versions of the web service. 10.2.3 Issuing a Web Service RequestYou can use the .NET Framework's networking and XML classes to write code to issue web service requests and handle the responses quite easily. First, I'll show you how to write the code yourself; then I'll show you how to use the .NET Framework to generate the code for you. 10.2.3.1 Issuing an HTTP GET requestOnce you have the InventoryQuery web service, it is possible to write a simple client that invokes the GetNumberInStock method over HTTP GET. Example 10-4 shows one possible implementation. Example 10-4. Program to access GetNumberInStock via HTTP GETusing System; using System.IO; using System.Net; using System.Xml.XPath; public class GetNumberInStockHttpGet { public static void Main(string [ ] args) { WebRequest request = WebRequest.Create("http://127.0.0.1/dotNetAndXml /InventoryQuery.asmx/GetNumberInStock?productCode=803B"); request.Method = "GET"; WebResponse response = request.GetResponse( ); Stream stream = response.GetResponseStream( ); XPathDocument document = new XPathDocument(stream); XPathNavigator nav = document.CreateNavigator( ); XPathNodeIterator nodes = nav.Select("//int"); Console.WriteLine(nodes.Current); } } This example uses several classes you've seen before, including WebRequest, Stream, and XPathNavigator, to send a web service request to a URI and parse the response. If it doesn't look fairly intuitive at this point, I'd suggest reviewing Chapter 2 for a refresher on basic I/O, Chapter 4 for HTTP requests, and Chapter 6 for XPath. The response is formatted as XML, as you saw the web service tester generated: <?xml version="1.0" encoding="utf-8" ?> <int xmlns="http://angushardware.com">0</int> Parsing this response is a simple matter with XPath. 10.2.3.2 Issuing an HTTP POST requestThe HTTP POST request is almost identical to the HTTP GET request, except that rather than including parameter values in the URL, they are sent to the server in the content of the HTTP request. Example 10-5 shows a program which uses HTTP POST to invoke the GetNumberInStock method. Example 10-5. Program to access GetNumberInStock via HTTP POSTusing System; using System.IO; using System.Net; public class GetNumberInStockHttpPost { public static void Main(string [ ] args) { string content = "productCode=803B"; HttpWebRequest request = (HttpWebRequest)WebRequest.Create( "http://127.0.0.1:80/dotNetAndXml/InventoryQuery.asmx/GetNumberInStock"); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.ContentLength = content.Length; StreamWriter streamWriter = new StreamWriter(request.GetRequestStream( )); streamWriter.Write(content); streamWriter.Flush( ); WebResponse response = request.GetResponse( ); Stream stream = response.GetResponseStream( ); XPathDocument document = new XPathDocument(stream); XPathNavigator nav = document.CreateNavigator( ); XPathNodeIterator nodes = nav.Select("//int"); Console.WriteLine(nodes.Current); } } In Example 10-5, the content variable holds the content of the POST request, and the response to the request is the same as for Example 10-4. Note that the Content-Type header of the HTTP POST request must be set to application/x-www-form-urlencoded, which is the same encoding used for submitting forms in a web browser. The content of the POST request takes the form of name/value pairs, with the name of the variable, followed by a = character and its value. The name/value pairs are separated from each other with the & character. Each name and value is further encoded as follows:
10.2.3.3 Issuing a SOAP requestLike the HTTP GET request, you can write a simple program to issue the SOAP request and handle the SOAP response. Example 10-6 shows one possible program to do this. Example 10-6. Program to generate GetNumberInStock request via SOAPusing System; using System.IO; using System.Net; using System.Xml; public class GetNumberInStockSoap { private const string soapNS = "http://schemas.xmlsoap.org/soap/envelope/"; private static readonly encoding = Encoding.UTF8; public static void Main(string [ ] args) { MemoryStream stream = new MemoryStream( ); XmlTextWriter writer = new XmlTextWriter(stream,encoding); writer.WriteStartDocument( ); writer.WriteStartElement("soap","Envelope",soapNS); writer.WriteStartElement("Body",soapNS); writer.WriteStartElement("GetNumberInStock",angusNS); writer.WriteElementString("productCode","803B"); writer.WriteEndElement( ); // GetNumberInStock writer.WriteEndElement( ); // soap:Body writer.WriteEndElement( ); // soap:Envelope writer.WriteEndDocument( ); writer.Flush( ); stream.Seek(0,SeekOrigin.Begin); StreamReader reader = new StreamReader(stream); string soap = reader.ReadToEnd( ); HttpWebRequest request = (HttpWebRequest)WebRequest.Create( "http://127.0.0.1/dotNetAndXml/InventoryQuery.asmx"); request.Method = "POST"; request.ContentType = "text/xml; charset=" + encoding.HeaderName; request.ContentLength = soap.Length; request.Headers["SOAPAction"] = "http://angushardware.com/InventoryQuery/ GetNumberInStock"; StreamWriter streamWriter = new StreamWriter(request.GetRequestStream( )); streamWriter.Write(soap); streamWriter.Flush( ); WebResponse response = request.GetResponse( ); Stream responseStream = response.GetResponseStream( ); XPathDocument document = new XPathDocument(responseStream); XPathNavigator nav = document.CreateNavigator( ); XPathNodeIterator nodes = nav.Select("//Envelope/Body/GetNumberInStockResponse/GetNumberInStockResult"); Console.WriteLine(nodes.Current); } } Example 10-6 bears a closer look. It consists of three major parts. The first part, shown here, creates the SOAP envelope using an XmlTextWriter instance wrapped around a MemoryStream, and stores it in a string variable named soap: MemoryStream stream = new MemoryStream( ); XmlTextWriter writer = new XmlTextWriter(stream,encoding); writer.WriteStartDocument( ); writer.WriteStartElement("soap","Envelope",soapNS); ... writer.WriteEndElement( ); // soap:Envelope writer.WriteEndDocument( ); writer.Flush( ); stream.Seek(0,SeekOrigin.Begin); StreamReader reader = new StreamReader(stream); string soap = reader.ReadToEnd( );
The second part creates the HTTP request. I'll step through it in smaller chunks, below: HttpWebRequest request = (HttpWebRequest)WebRequest.Create( "http://127.0.0.1/dotNetAndXml/InventoryQuery.asmx"); This line uses the WebRequest.Create( ) method to create an HTTP request for the URI http://127.0.0.1/dotNetAndXml/InventoryQuery.asmx, which is the URI to which the InventoryQuery web service is bound: request.Method = "POST"; A SOAP request can be sent over a variety of transports. However, an HTTP request must use the POST or PUT method in order to have content: request.ContentType = "text/xml; charset=" + encoding.HeaderName; A SOAP request must have XML content, and the character encoding rules must match that of the XML document. Since I created the XmlTextWriter by passing a Stream into the constructor, here I set the Content-Type header to text/xml and the same encoding I passed into the XmlTextWriter's constructor: request.ContentLength = soap.Length; When the HTTP request has content, the Content-Length header must be set to the length of the request's content: request.Headers["SOAPAction"] = "http://angushardware.com/InventoryQuery/GetNumberInStock"; To complete the HTTP headers, I set the SOAPAction header so that the web service provider knows which method is being called. Note that some SOAP implementations may require quotes around the URI, although they are optional in .NET. The third part, shown below, extracts the returned value from the SOAP response, using a familiar XPathNavigator with the XPath query //Envelope/Body/GetNumberInStockResponse/GetNumberInStockResult, and writes the result to the console: XPathDocument document = new XPathDocument(responseStream); XPathNavigator nav = document.CreateNavigator( ); XPathNodeIterator nodes = nav.Select("//Envelope/Body/GetNumberInStockResponse/ GetNumberInStockResult"); Console.WriteLine(nodes.Current); 10.2.4 Generating Client CodeOf course, you shouldn't have to build HTTP or SOAP requests by hand. And indeed, you don't; the .NET Framework SDK includes a tool, wsdl.exe, which can generate web service client code from any WSDL file. Run the command line wsdl /language:vb http://127.0.0.1/dotNetAndXml/InventoryQuery.asmx?WSDL to produce the Visual Basic .NET source code listed in Example 10-7 for the InventoryQuery service. Example 10-7. VB .NET client code for the InventoryQuery web service, using SOAP'------------------------------------------------------------------------------ ' <autogenerated> ' This code was generated by a tool. ' Runtime Version: 1.0.3705.288 ' ' Changes to this file may cause incorrect behavior and will be lost if ' the code is regenerated. ' </autogenerated> '------------------------------------------------------------------------------ Option Strict Off Option Explicit On Imports System Imports System.ComponentModel Imports System.Diagnostics Imports System.Web.Services Imports System.Web.Services.Protocols Imports System.Xml.Serialization ' 'This source code was auto-generated by wsdl, Version=1.0.3705.288. ' '<remarks/> <System.Diagnostics.DebuggerStepThroughAttribute( ), _ System.ComponentModel.DesignerCategoryAttribute("code"), _ System.Web.Services.WebServiceBindingAttribute(Name:="InventoryQuerySoap", [Namespace]:="http://angushardware.com/InventoryQuery")> _ Public Class InventoryQuery Inherits System.Web.Services.Protocols.SoapHttpClientProtocol '<remarks/> Public Sub New( ) MyBase.New Me.Url = "http://127.0.0.1/dotNetAndXml/InventoryQuery.asmx" End Sub '<remarks/> <System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://angushardware.com/ InventoryQuery/GetNumberInStock", RequestNamespace:="http://angushardware.com/ InventoryQuery", ResponseNamespace:="http://angushardware.com/InventoryQuery", Use:=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle:=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)> _ Public Function GetNumberInStock(ByVal productCode As String) As Integer Dim results( ) As Object = Me.Invoke("GetNumberInStock", New Object( ) {productCode}) Return CType(results(0),Integer) End Function '<remarks/> Public Function BeginGetNumberInStock(ByVal productCode As String, ByVal callback As System.AsyncCallback, ByVal asyncState As Object) As System.IAsyncResult Return Me.BeginInvoke("GetNumberInStock", New Object( ) {productCode}, callback, asyncState) End Function '<remarks/> Public Function EndGetNumberInStock(ByVal asyncResult As System.IAsyncResult) As Integer Dim results( ) As Object = Me.EndInvoke(asyncResult) Return CType(results(0),Integer) End Function End Class
I've used Visual Basic .NET for this example in part to emphasize the fact that a Web Services need not be written in the same language as the server. In reality, the client need not even be a Windows-based computer. Now that you've got the generated InventoryQuery proxy class, you can write a console application to use the proxy to call the web service. Example 10-8 shows one possible implementation in Visual Basic .NET. Example 10-8. Visual Basic .NET program to call the InventoryQuery proxy classClass InventoryQueryClient Shared Sub Main(byVal args as String( )) Dim query As InventoryQuery = New InventoryQuery( ) System.Console.WriteLine(query.GetNumberInStock(args(0))) End Sub End Class To compile this code outside of Visual Studio .NET, you'll need to use the following command line: vbc.exe /reference:Microsoft.VisualBasic.dll /reference:System.dll / reference:System.Web.Services.dll /reference:System.Xml.dll InventoryQueryClient.vb InventoryQuery.vb This method of creating Web Services client code hides all the details of the XML and HTTP from you, although it still requires you to implement the web service code on the server side (unless you're creating a client for some third party's web service). Obviously, this is a much easier way to create Web Services client code. Although the parameter list and behavior are identical, the InventoryQuery proxy class generated by wsdl.exe is not the same class I wrote in Example 10-2. To clarify the difference, you can specify the namespace for the generated proxy class by including the /namespace argument on the wsdl.exe command line. Also remember that the .asmx file looks in its .\Bin subdirectory for the assembly containing the InventoryQuery class that it uses to serve requests. 10.2.5 Building Requests with RemotingEven the automatically generated code requires you to write code specifically to serve web service requests. There is one more way to use Web Services to invoke methods across a distributed application. .NET Remoting puts together everything you've seen up to this point to form the very heart of .NET's distributed application framework.
There are three major differences between .NET Remoting and the previous Web Services examples. First, although Web Services uses the ASP.NET provider as the web service host, Remoting can run within any .NET application. Second, Remoting does not provide a WSDL file for the service, instead relying on the fact that server and client code are written specifically to work with each other. Finally, Remoting uses the runtime form of SOAP serialization I introduced in Chapter 9 rather than the SOAP serialization that Web Services uses. The first step in implementing a Remoting server is to alter the InventoryQuery class from Example 10-2 as follows. As you'll see, the only difference is that I've removed the WebService and WebMethod attributes, and made InventoryQuery derive from MarshalbyRefObject: using System; public class InventoryQuery : MarshalByRefObject { public int GetNumberInStock(string productCode) { return 0; } } The next step is to create a server to listen for requests to the InventoryQuery object. I'll call it InventoryQueryServer, and here's the code: using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; public class InventoryQueryServer { public static void Main(string [ ] args) { TcpChannel chan = new TcpChannel(8085); ChannelServices.RegisterChannel(chan); RemotingConfiguration.RegisterWellKnownServiceType( Type.GetType("InventoryQuery"), "GetNumberInStock", WellKnownObjectMode.Singleton); System.Console.WriteLine("Hit return to exit..."); System.Console.ReadLine( ); } } This program simply registers the service and waits for client requests. All that's left now is to write the client code: using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; public class InventoryQueryRemotingClient { public static void Main(string [ ] args) { TcpChannel chan = new TcpChannel( ); ChannelServices.RegisterChannel(chan); InventoryQuery query = (InventoryQuery)Activator.GetObject( Type.GetType("InventoryQuery"), "tcp://localhost:8085/GetNumberInStock"); Console.WriteLine(query.GetNumberInStock("803B")); } } That's a fairly sketchy overview of the Remoting process, but that topic moves beyond this book's realm. Programming.NET Components by Juval Löwy (O'Reilly) covers the topic more thoroughly than I can here. 10.2.6 Publishing a Web ServiceOnce you have set up your server to host a web service, you need to inform potential clients of its existence. Additionally, you might want to access a web service published by someone else. These are both jobs for UDDI.
10.2.6.1 The UDDI data modelThe UDDI data model, described in an XML Schema, consists of five basic information elements. The following lists the elements of the UDDI document:
10.2.6.2 The UDDI APIsSince UDDI is itself accessible as a web service, you can use .NET's tools to generate client code to access a UDDI registry. There are two SOAP APIs to access the UDDI registry: inquiry and publishing. I'll discuss inquiry first, and publishing in a moment. Inquiry involves searching the UDDI registry for a given business, service, or binding. After you find the information you're interested in, you need to get specific instances of UDDI registry objects. The Inquire API provides four methods to find entities and four to retrieve detailed information about a known entity. The following lists the find and get methods:
The UDDI Inquire API is described using WSDL at http://uddi.microsoft.com/inquire.asmx?WSDL, and the Publish API is at http://uddi.microsoft.com/publish.asmx?WSDL. You can use the wsdl tool to generate client code to access either of these services, and use the generated classes to find a business in the UDDI registry. The program in Example 10-9 finds any information for businesses whose names contain the string "bornstein". Example 10-9. Program to search the UDDI registry for business named "bornstein"using System; public class FindBornstein { public static void Main(string[ ] args) { InquireSoap inquireSoap = new InquireSoap( ); inquireSoap.Url = "http://test.uddi.microsoft.com/inquire"; name businessName = new name( ); businessName.Value = "bornstein"; find_business find = new find_business( ); find.name = new name [ ] { businessName }; find.generic = "2.0"; businessList businesses = inquireSoap.find_business(find); for (int i = 0; i < businesses.businessInfos.Length; i++) { businessInfo info = businesses.businessInfos[i]; Console.WriteLine("Business name: {0} ({2})", info.name[0].Value, info.name[0].lang); Console.WriteLine("Business key: {0}", info.businessKey); } } } The classes generated by wsdl.exe may seem a bit convoluted, but it only generates the classes as needed by the UDDI Inquire API. The fact that there are a find_business( ) method and a find_business class reflect the fact that the SOAP message is itself an object. You instantiate a find_business object and then send it to the UDDI server with the find_business( ) method. Publishing a web service involves registering a service with the UDDI registry service. Again, there is an API whose methods you can call. The UDDI Publishing API can be broken down into three general areas: assertion, authorization, and others. The following describes the assertion methods of the UDDI Publishing API, which deal with the relationships between business entities:
The authorization methods deal with authorization tokens. An authorization token represents a session between the UDDI registry operator and the client that is publishing information. get_registeredInfo is also included in this group:
The remainder of the methods deal with creating and deleting the UDDI registry objects. For each object type (bindingTemplate, businessEntity, businessService, and tModel), there are corresponding save and delete methods:
These web service methods can be accessed just as any web service method, by generating a proxy class using the wsdl tool, or by adding a web reference to your project in Visual Studio .NET. The Microsoft UDDI registry is also available for interactive searching and publishing via an HTML front end. You can access the registry at http://uddi.microsoft.com.
|
[ Team LiB ] |