8.5 Programming Against Interfaces
The previous section showed you how to define and implement interfaces. This section completes the picture by showing you how to code clients against those interfaces.
8.5.1 Generating Proxies from Interfaces
On the client side, a Web service proxy exposes the set of methods that reflects the binding's operations. As far as the client is concerned, this set of methods is considered the Web service's interface.
When you add a Web reference to an interface-only WSDL document, Visual Studio .NET recognizes an interface-only WSDL and generates the proxy class for you (beta versions didn't do this). Since the WSDL document doesn't contain a <service> element, the generated proxy class will not have a Web service URL in its constructor. You can also use wsdl.exe to generate the proxy class:
wsdl.exe /l:VB /out:MyClass.vb SingleInterface.wsdl
Listing 8.13 shows two example proxy classes one for IOrderMgmt and one for IQuoteMgmt. These proxies were generated using wsdl.exe.
Listing 8.13 Proxy classes generated based on IOrderMgmt and IQuoteMgmt interface definitions (VBWSClientCode\Chapter8\Interfaces\InterfaceClientSupplier3.vb)
<WebServiceBindingAttribute( _ Name:="IOrderMgmt", _ [Namespace]:="http://LearnXmlWS.com/OrderMgmt")> _ Public Class COrderMgmt Inherits SoapHttpClientProtocol Public Sub New() MyBase.New() End Sub <SoapDocumentMethod( _ "http://LearnXmlWS.com/Supplier/PlaceOrder", _ RequestNamespace:="http://LearnXmlWS.com/Supplier", _ ResponseNamespace:="http://LearnXmlWS.com/Supplier")> _ Public Function PlaceOrder(ByVal newOrder As Order) As String Dim results() As Object = Me.Invoke("PlaceOrder", _ New Object() {newOrder}) Return CType(results(0), String) End Function <SoapDocumentMethod( _ "http://LearnXmlWS.com/Supplier/CheckStatus", _ RequestNamespace:="http://LearnXmlWS.com/Supplier", _ ResponseNamespace:="http://LearnXmlWS.com/Supplier")> _ Public Function CheckStatus( _ ByVal OrderId As String) As OrderInfo Dim results() As Object = Me.Invoke("CheckStatus", _ New Object() {OrderId}) Return CType(results(0), OrderInfo) End Function End Class <WebServiceBinding( _ Name:="IQuoteMgmt", _ [Namespace]:="http://LearnXmlWS.com/QuoteMgmt")> _ Public Class CQuoteMgmt Inherits SoapHttpClientProtocol <SoapDocumentMethod( _ "http://LearnXmlWS.com/Supplier/GetPriceQuote", _ RequestNamespace:="http://LearnXmlWS.com/Supplier", _ ResponseNamespace:="http://LearnXmlWS.com/Supplier")> _ Public Function GetPriceQuote( _ ByVal newOrder As InterfaceClient.Order) _ As InterfaceClient.QuoteInfo() Dim results() As Object = Me.Invoke("GetPriceQuote", _ New Object() {newOrder}) Return CType(results(0), QuoteInfo()) End Function End Class
I specifically renamed the classes to begin with a "C" to make it clear that these are classes and not interfaces. The first class, COrderMgmt, represents the IOrderMgmt binding as indicated by its WebServiceBinding attribute. The class inherits from SoapHttpClientProtocol and adds two methods, PlaceOrder and CheckStatus, each with a SoapDocumentMethod attribute that defines the request and response namespaces as defined in the WSDL. Similarly, the second class, CQuoteMgmt, represents the IQuoteMgmt binding. It also inherits from SoapHttpClientProtocol and adds a function called GetPriceQuote as defined in the WSDL.
The classes in Listing 8.13 are definitely good enough for the client to call the service based on the two interfaces IOrderMgmt and IQuoteMgmt. However, some developers might want to take it a step further and program against interfaces, instead of classes, on the client side.
Unfortunately, WebServiceBinding cannot be applied to interfaces. One way around this is to define manually two interfaces on the client side called IOrderMgmt and IQuoteMgmt as shown in Listing 8.14.
Listing 8.14 Defining interfaces on the client side and creating a class that implements these interfaces and forwards method calls to the Web service proxy classes (VBWSClientCode\Chapter8\Interfaces\InterfaceClientSupplier3.vb)
Interface IOrderMgmt Function PlaceOrder(ByVal newOrder As Order) As String Function CheckStatus(ByVal OrderId As String) As OrderInfo End Interface Interface IQuoteMgmt Function GetPriceQuote(ByVal newOrder As Order) As QuoteInfo() End Interface Public Class SupplierProxy Implements IOrderMgmt Implements IQuoteMgmt Private _ordermgmt As COrderMgmt Private _quotemgmt As CQuoteMgmt Public Function CheckStatus( _ ByVal OrderId As String) As OrderInfo _ Implements InterfaceClient.IOrderMgmt.CheckStatus Return _ordermgmt.CheckStatus(OrderId) End Function Public Function PlaceOrder(ByVal newOrder As Order) _ As String _ Implements InterfaceClient.IOrderMgmt.PlaceOrder Return _ordermgmt.PlaceOrder(newOrder) End Function Public Function GetPriceQuote( _ ByVal newOrder As Order) As QuoteInfo() _ Implements InterfaceClient.IQuoteMgmt.GetPriceQuote Return _quotemgmt.GetPriceQuote(newOrder) End Function Public Sub New(ByVal Url As String) _ordermgmt = New COrderMgmt() _quotemgmt = New CQuoteMgmt() _ordermgmt.Url = Url _quotemgmt.Url = Url End Sub End Class
Each interface includes methods of the corresponding binding as defined in the WSDL document. A class, called SupplierProxy, implements both interfaces and contains instances of COrderMgmt and CQuoteMgmt. You can build the code in Listing 8.14 in a separate .dll and distribute it to client developers as a prepackaged interface-based proxy for your Web service. This is valuable if you are responsible for maintaining the Web service and the proxy classes and you want to abstract client developers from the details of your Web service's interface.
Listing 8.15 shows example client code that is designed to work against the interfaces IOrderMgmt and IQuoteMgmt.
Listing 8.15 An example client that works against the interfaces not the Web service proxy (VBWSClientCode\Chapter8\Interfaces\InterfaceClientForm1.vb)
Private Const SERVICE_URL As String = _ "http://VBWSServer/vbwsbook/Chapter8/Supplier3.asmx" Private Sub btnPlaceOrder_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnPlaceOrder.Click Dim ws As IOrderMgmt ws = New SupplierProxy(SERVICE_URL) ws.PlaceOrder(MakeOrder()) End Sub Private Sub btnQuote_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnQuote.Click Dim ws As IQuoteMgmt ws = New SupplierProxy(SERVICE_URL) ws.GetPriceQuote(MakeOrder()) End Sub
To invoke the methods of the IOrderMgmt binding, the client declares a variable of type IOrderMgmt, then sets it to a new instance of SupplierProxy and proceeds to call PlaceOrder or CheckStatus. Similarly, to call methods of IQuoteMgmt, the client declares a variable of type IQuoteMgmt and sets it to a new instance of SupplierProxy and calls GetPriceQuote.
The code in Listing 8.15 makes it very clear that the client is programmed against an interface and not a specific implementation. However, there's still one glaring problem with this code: The Web service URL, which is the location of a specific implementation of IOrderMgmt and IQuoteMgmt, is hard-coded as a constant. The next sections deal with determining the Web service URL at run-time based on external configuration settings.