.NET Web Services: Interface-Based Web Service Development
Chapter 8 Interface-Based Web Service Development
The goal of education is the advancement of knowledge and the dissemination of truth. John F. Kennedy
Interface-based programming was popularized with component-based development in the 1990s. Using technologies like COM, you could define an interface and have several components implement it. Clients could utilize any of those components by programming against the interface. As your Web services evolve and mature, you will find it necessary to factor out Web service methods into interfaces, implement existing standard interfaces on your Web services, and program clients against an interface rather than a specific Web service. Interfaces can also be useful for versioning Web services by leaving the old interface intact and implementing a new interface on the same service.
WSDL bindings make this possible. In Chapter 4 you learned about WSDL bindings and how they define a concrete set of operations and provide the information needed to invoke those operations. A Web service implements one or more bindings and exposes them at a particular location defined by the port. Even if you haven't read Chapter 4, you can read this chapter and learn how to do interface-based programming. However, you will gain much more from this chapter if you read Chapter 4 first.
8.1 Defining Interfaces
The first step in interface-based programming is to define the interfaces you want to implement. When you build a Web service, you should always start with defining the interface. Today, tools like Visual Studio .NET do not provide direct support for this. I am hopeful that future versions will provide the needed support for defining Web service interfaces.
Although you can use Notepad to create a WSDL document from scratch, you'll probably want a more productive and less error-prone way to define your interfaces. An easy way to define a Web service interface is to create a Web service and define the Web methods you want the interface to have. If you have parameters with complex types, you define those types in schemas, then use xsd.exe to generate classes from the schemas (see Chapter 2 for more information on xsd.exe).
By default, all of a Web service's methods belong to the same binding. That binding (interface) has the same name as the Web service class with the word Soap appended. If you've created COM components in Visual Basic, you may know that each component you create has a default interface that is given the name _ClassName. Therefore, the concept of auto-generated interfaces shouldn't be new to you.
To control the binding's name and namespace, you use the WebServiceBinding attribute on the Web service class to specify that binding's name and namespace. On each Web method that the service exposes, you add SoapDocumentMethod or SoapRpcMethod and set its Binding property to the binding name. Listing 8.1 shows an example class called SupplierIface1 that exposes its methods in a binding called ISupplier.
Listing 8.1 A Web service example that exposes a binding called ISupplier (VBWSBook\Chapter8\Supplier1.asmx.vb)
Namespace Supplier1 Public Structure Order Public CustomerEmail As String Public ShipVia As Shipper Public ShipName As String Public ShipAddress As String Public ShipCity As String Public ShipState As String Public ShipZipCode As String Public OrderItems() As OrderItem 'array of OrderItems End Structure Public Structure OrderItem Public ProductID As Integer Public Quantity As Integer End Structure Public Enum Shipper FedEx = 1 UPS USPS End Enum Public Enum OrderStatus Pending Shipped Delivered End Enum Public Structure OrderInfo Public Status As OrderStatus Public ShippingType As String Public DeliveredDate As Date Public DeliveredTo As String End Structure Public Structure QuoteInfo Public ProductCost As Double Public Tax As Double Public Shipping As Double Public TotalCost As Double End Structure <WebServiceBinding( _ Name:="ISupplier", _ [Namespace]:="http://LearnXmlWS.com/Supplier"), _ WebService([Namespace]:="http://LearnXmlWS.com/Supplier", _ Description:="The supplier's Web service")> _ Public Class SupplierIface1 Inherits System.Web.Services.WebService <WebMethod( _ Description:= _ "Places the order then returns the new order id"), _ SoapDocumentMethod(Binding:="ISupplier")> _ Public Function PlaceOrder(ByVal newOrder As Order) _ As String 'returns a new order id End Function <WebMethod(), _ SoapDocumentMethod(Binding:="ISupplier")> _ Public Function CheckStatus(ByVal OrderId As String) _ As OrderInfo 'returns an orderinfo structure End Function <WebMethod(), _ SoapDocumentMethod(Binding:="ISupplier")> _ Public Function GetPriceQuote(ByVal newOrder As Order) As QuoteInfo 'returns an orderinfo structure End Function End Class End Namespace
The first part of Listing 8.1 defines the data types that will be used by the service methods (for example, Order, OrderItem, Shipper, OrderStatus, OrderInfo, and QuoteInfo). The SupplierIface1 class has two attributes applied to it. WebServiceBinding has its name property set to ISupplier and its namespace property set to http://LearnXmlWS.com/Supplier. Each of the Web service methods has a SoapDocumentMethod applied to it with the Binding property set to ISupplier, making the methods part of the interface called ISupplier. The resulting WSDL document contains the binding definition and the service definition.
To get a pure interface definition, you can save this WSDL document to disk and remove the <service> element, which specifies a particular implementation for the interface. The edited WSDL document, shown in Listing 8.2 now contains your interface definition, which you can give to other developers who can use it to implement the same binding (interface) on their services.
Listing 8.2@The interface WSDL after removing the <service> element (VBWSBook\Chapter8\SingleInterface.wsdl)
<?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://LearnXmlWS.com/Supplier" 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://LearnXmlWS.com/Supplier" xmlns="http://schemas.xmlsoap.org/wsdl/"> <types> <s:schema elementFormDefault="qualified" targetNamespace="http://LearnXmlWS.com/Supplier"> <s:element name="PlaceOrder"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="newOrder" type="s0:Order" /> </s:sequence> </s:complexType> </s:element> <s:complexType name="Order"> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="CustomerEmail" type="s:string" /> <s:element minOccurs="1" maxOccurs="1" name="ShipVia" type="s0:Shipper" /> <s:element minOccurs="0" maxOccurs="1" name="ShipName" type="s:string" /> <s:element minOccurs="0" maxOccurs="1" name="ShipAddress" type="s:string" /> <s:element minOccurs="0" maxOccurs="1" name="ShipCity" type="s:string" /> <s:element minOccurs="0" maxOccurs="1" name="ShipState" type="s:string" /> <s:element minOccurs="0" maxOccurs="1" name="ShipZipCode" type="s:string" /> <s:element minOccurs="0" maxOccurs="1" name="OrderItems" type="s0:ArrayOfOrderItem" /> </s:sequence> </s:complexType> <s:simpleType name="Shipper"> <s:restriction base="s:string"> <s:enumeration value="FedEx" /> <s:enumeration value="UPS" /> <s:enumeration value="USPS" /> </s:restriction> </s:simpleType> <s:complexType name="ArrayOfOrderItem"> <s:sequence> <s:element minOccurs="0" maxOccurs="unbounded" name="OrderItem" type="s0:OrderItem" /> </s:sequence> </s:complexType> <s:complexType name="OrderItem"> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="ProductID" type="s:int" /> <s:element minOccurs="1" maxOccurs="1" name="Quantity" type="s:int" /> </s:sequence> </s:complexType> <s:element name="PlaceOrderResponse"> <s:complexType> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="PlaceOrderResult" type="s:string" /> </s:sequence> </s:complexType> </s:element> <s:element name="CheckStatus"> <s:complexType> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="OrderId" type="s:string" /> </s:sequence> </s:complexType> </s:element> <s:element name="CheckStatusResponse"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="CheckStatusResult" type="s0:OrderInfo" /> </s:sequence> </s:complexType> </s:element> <s:complexType name="OrderInfo"> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="Status" type="s0:OrderStatus" /> <s:element minOccurs="0" maxOccurs="1" name="ShippingType" type="s:string" /> <s:element minOccurs="1" maxOccurs="1" name="DeliveredDate" type="s:dateTime" /> <s:element minOccurs="0" maxOccurs="1" name="DeliveredTo" type="s:string" /> </s:sequence> </s:complexType> <s:simpleType name="OrderStatus"> <s:restriction base="s:string"> <s:enumeration value="Pending" /> <s:enumeration value="Shipped" /> <s:enumeration value="Delivered" /> </s:restriction> </s:simpleType> <s:element name="GetPriceQuote"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="newOrder" type="s0:Order" /> </s:sequence> </s:complexType> </s:element> <s:element name="GetPriceQuoteResponse"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="GetPriceQuoteResult" type="s0:QuoteInfo" /> </s:sequence> </s:complexType> </s:element> <s:complexType name="QuoteInfo"> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="ProductCost" type="s:double" /> <s:element minOccurs="1" maxOccurs="1" name="Tax" type="s:double" /> <s:element minOccurs="1" maxOccurs="1" name="Shipping" type="s:double" /> <s:element minOccurs="1" maxOccurs="1" name="TotalCost" type="s:double" /> </s:sequence> </s:complexType> </s:schema> </types> <message name="PlaceOrderSoapIn"> <part name="parameters" element="s0:PlaceOrder" /> </message> <message name="PlaceOrderSoapOut"> <part name="parameters" element="s0:PlaceOrderResponse" /> </message> <message name="CheckStatusSoapIn"> <part name="parameters" element="s0:CheckStatus" /> </message> <message name="CheckStatusSoapOut"> <part name="parameters" element="s0:CheckStatusResponse" /> </message> <message name="GetPriceQuoteSoapIn"> <part name="parameters" element="s0:GetPriceQuote" /> </message> <message name="GetPriceQuoteSoapOut"> <part name="parameters" element="s0:GetPriceQuoteResponse" /> </message> <portType name="ISupplier"> <operation name="PlaceOrder"> <documentation> Places the order then returns the new order id </documentation> <input message="s0:PlaceOrderSoapIn" /> <output message="s0:PlaceOrderSoapOut" /> </operation> <operation name="CheckStatus"> <input message="s0:CheckStatusSoapIn" /> <output message="s0:CheckStatusSoapOut" /> </operation> <operation name="GetPriceQuote"> <input message="s0:GetPriceQuoteSoapIn" /> <output message="s0:GetPriceQuoteSoapOut" /> </operation> </portType> <binding name="ISupplier" type="s0:ISupplier"> <soap:binding transport= "http://schemas.xmlsoap.org/soap/http" style="document" /> <operation name="PlaceOrder"> <soap:operation soapAction="http://LearnXmlWS.com/Supplier/PlaceOrder" style="document" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> <operation name="CheckStatus"> <soap:operation soapAction="http://LearnXmlWS.com/Supplier/CheckStatus" style="document" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> <operation name="GetPriceQuote"> <soap:operation soapAction= "http://LearnXmlWS.com/Supplier/GetPriceQuote" style="document" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> </binding> </definitions>