So far we have only scratched the surface of real-world Web service programming. We still have to cover a number of significant aspects to complete our overview of the Web service development model. I’m not intending to offer you the ultimate book about Web service planning and programming. In spite of this, I want to discuss a few more arguments that become topical issues as soon as you move away from point-and-click Web services.
In the rest of this chapter, I will not delve into the nitty-gritty details of issues such as contract design, user authentication, and asynchronous calls. However, I’ll review the critical aspects of these issues so that you have a head start in finding out more information about them from sources such as MSDN online documentation. You’ll find these issues much easier to handle when you understand the big picture.
Defining the programming interface of a Web service might look like a deceptively easy task. Through an interface, a server and a client draw up a contract. The programming interface of a Web service is not much different from a contract between people. And as in contracts between people, a contract between a server and a client contains a lot of clauses and terms, intricacies and quirky language. By exposing the programming interface, a Web service asks prospective clients to look at the conditions set in the public WSDL script before deciding whether to adhere to the contract.
The excess of legalese in the contract that a Web service submits to its interested consumers can constitute a serious threat for the success of the service. A programming contract between a Web service and a client should be effective, which does not necessarily mean simple and succinct. Rather, it means that the programming interface of the Web service must be appropriate to the level and the complexity of the data being exchanged and the operations being requested and executed.
Each call addressing a Web service method requires a round-trip. Since all Web service activity takes place over the Internet, you cannot always expect a rapid response. Be prepared to measure the time needed for Web services calls in seconds rather than in milliseconds, meaning that your primary goal in designing a Web service is to minimize round-trips.
Since the round-trip is permanently tied to the request of an operation on the Web service, the best—and possibly the only—way to minimize round-trips is to merge more logically distinct functions. The open issue in this approach concerns using additional methods to the interface or additional parameters to the prototype of certain methods. Simple, succinct, and direct methods enhance overall design but certainly do not minimize round-trips, because to execute two functions, you need at least two round-trips.
On the other hand, incorporating more functionality in the body of a single and more complex method is effective in terms of performance but not necessarily in terms of the service usability. A client might receive more information than needed, paying the price of increased downloading time. Moreover, a client might be forced to use an overly complex signature, exposing itself to the risk of getting the requested information by trial and error. In such a case, you actually create more round-trips for the client.
Just as in life, in Web service programming, the truth lies somewhere in between. You should avoid method signatures that are too simple and too complex. This advice seems to contradict one of the golden rules for effective MTS/COM+ programming: write simple, stateless, and possibly parameterless methods. For Web services, use sophisticated (but not too complicated) methods that can incorporate functions with related parameters. Verify the complexity of the server-side code that validates the parameters and then execute the method, and make sure users don’t receive too much information that is not pertinent to their original requests. (Certainly ensure that this unrelated information is not the most relevant part of a response.) Finally, don’t forget that the more complex the programming interface is, the more you need effective and well-designed documentation.
At the highest level of abstraction, a Web service is a publicly accessible module that dispenses services to software consumers. However, in reality, not all software agents that can access the service have the rights to access it, nor can they invoke all the methods. A user must be authenticated to validate its identity to the system. After a user’s credentials are authenticated, the authorization process determines whether that user has access to a resource based on role information and the user’s profile.
The Web service infrastructure does not mandate that you implement authentication and authorization by using a made-to-measure API or approach. Instead the Web service leaves authentication and authorization up to the application, which should exploit the security features built into the application’s platform and protocol. The .NET-specific infrastructure for Web services does not provide ad-hoc facilities either. However, a Web service class that inherits from the WebService class can easily interact with the authentication mechanism of ASP.NET. ASP.NET, in turn, relies heavily on the IIS authentication mechanisms for HTTP, including basic, digest, and Windows-integrated authentication.
In ASP.NET, you retrieve a user identity by using a class that is specific to the authentication mode you choose. ASP.NET supports three authentication modes, which you set in the web.config file: Windows integrated (the default), form-based, and Microsoft Passport. These authentication modes rely on the WindowsIdentity, FormsIdentity, and PassportIdentity classes to represent authenticated users.
Using IIS-based authentication mechanisms with a Web service is fairly easy and codeless. All you have to handle is some configuration work to enable IIS to perform authentication before the Web service is called. You need to create accounts for each client that is allowed to access the Web service, and for each of them predispose the proper ACL on the .ASMX Web service file.
Authentication and authorization in .NET is not something optimized only for Web services. Both are designed to accommodate all applications running on the .NET platform. In most cases, a Web service can implement a customized security model that is also relatively simple to code and effective. The advantage of a handcrafted security model is that it does not require any sort of interaction with external components, including system components.
You might implement your own security layer if you already have, or find more manageable to use, a table of user account information. When you use your own security layer, you can use your own database instead of creating Windows accounts for each user. You might also use your own security layer when the results of the authentication and authorization depend on run-time conditions and data. Let’s review a consolidated approach for building a custom security layer.
Your Web service might require all its clients to log on prior to calling any methods. The signature of the method for logging on might look like the following:
long Login(String uid, String pswd, String role)
The method returns a value that works as a token to call other methods. The information stored in the token, as well as its data type, is completely up to you. The token should have some basic characteristics such as being encrypted, or at least not easily read by another person. In addition, you should be able to extract from it all the information needed to verify the authenticity of the token and the user prior to calling the requested method. The following pseudocode shows how it works:
long token = MyService.Login(uid, pswd, role); MyService.GetEmployee(token, nEmpID);
The Login method must authenticate the user against a database or any persistent support you might want to consider. It then creates and returns a token that summarizes information about the user and the time the token was generated. Each method that requires the token first checks its validity before resuming execution. Of course, token-free methods are not subject to restriction rules.
For Web services, relying on HTTP is advantageous because requests and responses pass through most firewalls, which normally accept packets coming through port 80. Other network protocols such as DCOM encounter serious interoperability problems because particularly restrictive firewalls won’t allow all the requested traffic. However, relying only on HTTP makes maintaining the state between successive calls difficult for Web services. HTTP, in fact, is a stateless protocol.
Web services in .NET handle state management in much the same way other ASP.NET applications do. Both Web services and ASP.NET applications have access to the same built-in options, that is, the Session, Application, and Cache objects; and both can employ other programming techniques such as storing data on disk files or in database tables. To access ASP.NET state objects, the Web service must be derived from the WebService class.
Web services built atop the WebService class automatically have access to the Application and Cache objects. All data stored in the Session object is available only when you set the EnableSession property of the WebMethod attribute to true.
Defining a standard way to publish and locate information about Web services is the ultimate goal of the UDDI specification (http://www.uddi.org). The UDDI Business Registry is a central repository—a sort of Web services yellow pages—where organizations can store discovery documents and service descriptions about their Web services. The same registry can be accessed programmatically by potential clients that want to discover whether a Web service with certain characteristics already exists.
A Web service can be discovered when it is associated with a discovery file (.DISCO). The discovery file is an XML document that contains links to other resources that describe the Web service. In addition to being located in the UDDI Registry, discovery files can be located in the root of the Web server that hosts the Web service. The following is the content of the .DISCO file for the Northwind Info Service:
<?xml version="1.0" encoding="utf-8"?> <discovery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/disco/"> <contractRef xmlns="http://schemas.xmlsoap.org/disco/scl/" ref="http://expo-one/bwslib/chap09/nwservice/nwservice.asmx?wsdl" docRef="http://expo-one/bwslib/chap09/nwservice/nwservice.asmx" /> <soap address="http://expo-one/bwslib/chap09/nwservice/nwservice.asmx" xmlns:q1="bwslib/0735615780" binding="q1:Northwind Info ServiceSoap" xmlns="http://schemas.xmlsoap.org/disco/soap/" /> </discovery>
The <contractRef> node contains the link to the WSDL description of the functions available in the Web service. Learning what the Web service capabilities are and how to properly interact with the service is the first step for accessing a Web service.
The deployment of a Web service is rather simple and, compared to the deployment of Web applications before .NET was available, straightforward. You copy on the Web server the .ASMX file and any assembly it needs, with the obvious exceptions being the assemblies that are already part of the .NET Framework. Nonstandard assemblies have to be copied in the bin folder under the virtual directory for installation. The .DISCO file goes to the root directory of the application.