IntentProvide interface-based client programming to the Web services world. Abstract a Web service class implementation using an interface (interface-based interaction). Why is this common pattern considered advanced and a part of this chapter? The structure behind the Web Service Interface (WSI) pattern is simple to implement and could have been discussed in the implementation patterns section earlier in the book. However, this was moved to this chapter for two reasons. First, because most Web service clients will use the facilities provided by Visual Studio .NET, I felt that any implementation that required manual editing of the generated Web service proxy code could be considered an advanced option. This will be explained shortly. Second, this pattern lends itself to a very rudimentary design pattern of interfaced -based programming and thus can also be considered either an architectural pattern or a technology-specific design (implementation) pattern. This is where pattern classification becomes difficult and why I try as much as possible not to classify my patterns. Patterns should not be pigeonholed. ProblemImplementing a Web service in the .NET is rather simple, even when delving into more advanced functionality. By simply adding the Web service attribute to a System.Web.Services.WebService derived class, along with a Web method attribute marked operation, you can be off and running. Even more advanced Web service functionality, such as tracing input or receiving requestor information, has been made painless by the .NET CLR. However, the out-of-the-box implementation of the Web service framework forces the client to declare a reference of the Web service class when interacting with a Web service class or its generated proxy. For every Web service class implementation, each client must then declare and compile a class-based reference and be coupled to its specific method implementations . Currently, the .NET framework does not a provide means of abstracting a defined Web service, at least not by default. It would be helpful if there were a way of abstracting Web service implementations so that the Web service client could interact with this abstraction. Those already familiar with interface-based programming have come to appreciate the ability to decouple their clients from method to method implementation details of any server with which they may be interacting. For something as commonplace as interface-based programming, you would think that this would be straightforward in .NET when implementing a Web service. For example, it is more straightforward to trace SOAP messages than it is to abstract a Web service (at least from the Web service client perspective). I guess no framework is perfect, and .NET has come close to providing almost everything else our hearts desire , so cutting Microsoft some slack would not be a bad idea. Fortunately, there is a way to provide an abstract-interface-based programming model to the world of .NET Web services. ForcesUse the WSI Pattern when:
StructureThe structure of this pattern (Figure 7.3) is very similar to any other Web service class structure, other than the fact that there is an additional Web service class acting as the interface. This additional Web service class is actually an abstract class, and it will never be called. This additional entity represents only the description from which you can then obtain an interface definition. As you will see, it does not matter where you place the client-side interface that represents the abstracted Web service. The interface can go in some shared assembly or can remain local; the choice is yours. There is no trickery to making this work. The point of creating the additional Web service interface abstract class on the server is so that when the WSDL file is accessed and the client proxy code is generated, you now have a " server-controlled " version of this interface contract to work with. However, this entity is optional. As long as the interface is documented, the client can use any implemented interface code, provided that it matches the documented signature. Invoking any Web service class that implements the Web service interface is the same except that the client now casts the return value to the Web service interface. From there, the client can employ any Web service using polymorphism through this interface. Figure 7.3. Web Service Interface generic class diagram.
The following class model (Figure 7.3) will be explained more in the implementation section, which should clear up any confusion. ConsequencesThe WSI pattern has the following benefits and liabilities:
Participants
ImplementationListing 7.8 and Figure 7.4 show just one of the many implementation examples that can benefit from the WSI pattern. Sticking with our credit card system, an interface called IService has been created that contains a method called Execute . This method is passed a DataSet containing the necessary credit card information to authorize a transaction. The Execute method acts as a factory method for the remainder of the financial component required to perform the unit of work. This interface is the starting point of this pattern. This becomes the "contract" upon which the abstract Web service interface, called FinancialServiceFactory , implements and mimics by externally exposing itself as IService. Using the WSI pattern, we can now abstract all services using the IService as the interface contract with which all Web service clients will interact. Figure 7.4. WSI implementation class diagram.
The code in Listing 7.7 shows our simple interface, which happens to be defined in FinancialServiceFactory.asmx.cs. Listing 7.7 Sample interface.interface IService { // any signature can be used DataSet Execute(DataSet ds); } Figure 7.4 shows one concrete implementation of the IService interface called CreditCardService . The CreditCardService acts like any other Web service class but must implement IService, as shown in Listing 7.8. Listing 7.8 One concrete WSI implementation example.[WebService(Namespace="http://www.etier.com/patterns.net", Description = "This provides the first implementation of the Web Service Interface, Any Concrete Implementation will do.")] public class CreditCardService : System.Web.Services.WebService, IService { public CreditCardService() { InitializeComponent(); } . . . [WebMethod(Description = "Implements Execute")] public int Execute() { . . . } . . . } FinancialServerFactory is the abstract class that is externally represented as the IService interface, as explained in the previous sections. Using the Name property of the WebService attribute, we define IService as what will be represented in the generated WSDL when referencing the FinancialServiceFactory Web service from the client. The FinancialServiceFactory code is shown in Listing 7.9. Listing 7.9 Web service "piece" of the WSI implementation.[WebService(Name = "IService", Namespace="http://www.etier.com/patterns.net", Description = "This web service is abstract and cannot be directly called.")] abstract class WebServiceInterface : ICanBeAnyInterface { [WebMethod(Description = "Defines as a WebService Interface Execute from IService")] abstract public int Execute(); } Once these service elements are defined, the client can now interact with the IService interface directly, with one minimal change. The final adjustment that must be made on the client is to modify the generated proxy for each concrete Web service (CreditCardService, in our case). Simply set IService as the implemented interface in the actual proxy code once it is generated from WSDL. Listing 7.10 shows where you must add the IService interface before you can begin interacting with any concrete implementations. Listing 7.10 WSDL-generated proxy code ”highlighting where to add interface declaration. [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute(Name= "CreditCardServiceSoap", namespace="http://www.etier.com/patterns.net")] public class CreditCardServiceSoap : System.Web.Services.Protocols.SoapHttpClientProtocol, IService { /// <remarks/> public CreditCardService() { . . . } /// <remarks/> . . . } The interface IService must be defined somewhere on the client. You can define this manually, reference it directly, or infer it from the WSDL generated from the abstract Web service we created earlier (FinancialServiceFactory). Once you've defined the IService interface on the client, you may interact with any Web service using the IService interface. A simple interaction is shown in Listing 7.11. Listing 7.11 Client-side implementation sample of WSI.IService oWSI; DataSet oDsIn = null; DataSet oDsOut = null; // instantiate each web service using the interface we are now externalizing oWSI = (IService) new localhost.CreditCardService(); . . . // any interface method calls are polymorphic oDsOut = oWSI.Execute(oDsIn); . . . As you can the see, the Web service can now be treated like any other implementation class that implements any interface. Although there are a few hoops to leap through initially, the benefits significantly outweigh the hassles of providing you this useful pattern. |