Web Services: Building Reusable Web Components with SOAP and ASP.NET
|David S. Platt|
|This article assumes you’re familiar with XML and Visual Basic|
|Level of Difficulty 1 2 3|
|Download the code for this article: WebComp.exe(93KB)|
|Browse the code for this article at Code Center: TimeServiceDemo|
|SUMMARY XML and HTTP are cross-platform technologies especially suited for building applications that can communicate with each other over the Internet, regardless of the platform they are running on. Web Services in the Microsoft .NET Framework make it easy to write components that communicate using HTTP GET, HTTP POST, and SOAP.|
An understanding of these concepts, along with knowledge of synchronous and asynchronous operations, security, state management, and the management of proxies by the .NET Framework is essential in building these applications.
This article has been adapted from David Platt’s upcoming book introducing the Microsoft .NET Platform to be published by Microsoft Press in Spring 2000.
| seismic shift is just now beginning in Internet programming. Internet access will soon be built into nearly every program anyone ever writes. You won't use a generic browser, except when you feel like browsing generically. Instead, you will use dedicated programs that are optimized for accomplishing specific tasks. You won't be aware of the program's Internet access (except when it breaks).|
An early example of this type of program is Napster, which allows you to search the shared hard drives of thousands of participating users for music files that meet specified criteria, and download the ones you like. The dedicated user interface of a multi-player game is another example of hidden Internet access. And the latest edition of Microsoft® Money does a good job of seamlessly blending Web content (current stock quotes, latest balances, and so on) and desktop content (financial plans you create locally).
In order to develop programs of this type, there needs to be a quick and easy way (which, in turn, means a cheap way) to write code that communicates with other programs over the Internet. The idea isn't new; there are a number of existing technologies that enable this type of communication: RPC, DCOM, and Microsoft Message Queue Services (MSMQ). Each of these techniques is cool in itself, but they all share one fatal failing: they only work from one similar system to another. MSMQ talks only to MSMQ, a DCOM client only to a DCOM server, and so on.
What's really needed is universal programmatic access to the Internet—some way for a program on one box to talk to any other program on any other box. It has to be independent not only of the operating system, but also of the programs' internal implementations. (Is it C++ or Visual Basic®? Which vendor is it from? Which version is it? Right now you can barely solve this problem on a single desktop.) And it has to be easy to program, or no one will be able to afford it.
The only way to deal with the enormous numbers of heterogeneous entities on the Internet is to use the lowest common denominator. In other words, when bytes are transferred from one box to another, the process needs to use some standard that everyone on the Internet supports. The most common Internet transfer protocol is HTTP, which is used today by essentially all Web browsers to request the pages they display. The emerging cross-platform standard for encoding pure information transferred over HTTP is XML.
Microsoft put these ideas together and developed the concept of a Web Service—a seamless way for objects on a server to accept incoming requests from clients using HTTP and XML. To create a Web Service, you simply write a Microsoft .NET server object as if it were going to be accessed directly by local clients, mark it with an attribute that says that you want it to be available to Web clients, and let ASP.NET do the rest. It automatically hooks up a prefabricated infrastructure that accepts incoming requests through HTTP and maps them to calls on your object, as shown in Figure 1. By rolling them into a Web Service, your objects can work with anyone on the Web who speaks HTTP and XML, which should be everybody. You do not have to write the infrastructure that deals with Web communication—the operating system provides it for you.
Figure 1 Handling HTTP Requests
On the client side, .NET provides proxy classes that have easy, function-based access to the Web Services provided by any server that accepts HTTP requests, as shown in Figure 2. A developer tool reads the description of the Web Service and generates a proxy class containing functions in whatever language you're using to develop the client. When your client calls one of these functions, the proxy class generates an HTTP request and sends it to the server. When the response comes back from the server, the proxy class parses the results and returns them from the function. This allows your function-based client to seamlessly interact with any Web server that speaks HTTP and XML.
Figure 2 Proxy Classes
Writing a Web Service
As I always do when explaining a new piece of technology, I wrote the simplest program I could to demonstrate Web Services. This sample Web Service provides the current time of day on its machine in the form of a string, either with or without the seconds digits. You can download the sample code for this service from the link at the top of this article to follow this description more closely. You will need to download and install the Microsoft .NET SDK if you want to run it as well.
I wrote this Web Service in the form of an .asmx page to avoid distracting you with other software packages. I first installed the .NET SDK. Then I used the administrative tools in Microsoft Internet Information Services (IIS) to set up a virtual directory pointing to the folder in which I would do my service file development. Then I wrote my ASP.NET code in a file called TimeService.asmx on my server.
Writing the Web Service was incredibly easy; I did it in Notepad (see Figure 3). While it's quite simple, there are a few constructs that are probably new to you, so let's go over it section by section. (You should note that the code in this article is based on pre-Beta 1 bits and the calls may change somewhat before the final release.) The program starts with the standard ASP.NET salutation <%@...%>. In it, the WebService directive tells ASP.NET that the code on the page is to be exposed as a Web Service. The Language attribute tells ASP which language to compile the page's code in accordance with. I've chosen Visual Basic because it's familiar to the majority of developers. ASP.NET will use Visual Basic.NET to compile the code. You do not have to install Visual Studio®; all the language compilers come with the .NET SDK. The Class attribute tells ASP.NET the name of the class of object to activate for incoming requests addressed to this service.
The rest of the page contains the code that implements the class. I first use the Imports directive, a new feature of Visual Basic.NET, which tells the compiler to import the namespaces. The term "namespace" is a fancy way to refer to the description of a set of prefabricated functionality. It is conceptually identical to a reference in your Visual Basic 6.0 project. Since ASP.NET will compile this example's code "just-in-time" when a request arrives from a client, I don't have a project in which to set these references, so I have to explicitly add them to the code. The names following Imports tell the compiler which sets of functionality to include the references for. In this case, System and System.Web.Services contain the prefabricated features you need to write a Web Service.
The next line defines the name of the class. This class in Visual Basic is similar to many you've written. You'll see a brand new keyword (Inherits) at the end of the line: Inherits WebService. This represents one of the main enhancements in Visual Basic.NET—support for the object-oriented concept called inheritance. When you say that your new TimeService class inherits from the WebService class, you are telling the compiler to take all the code from the system-provided class named WebService (known as the base class) and include it in your new TimeService class (known as the derived class). And in addition to reusing the code from the class you inherit from, you can add, alter, and override the functionalities of that class in your new clan. Think of inheritance as cutting and pasting without actually moving anything. In fact, seriously deranged C++ and Java-language geeks often refer to physically cutting and pasting code as editor inheritance.
The WebService class is provided by the new .NET runtime library, just as many objects used in Visual Basic 6.0 were provided with the operating system. WebService contains all the prefabricated plumbing required to handle the incoming HTTP requests and route them to the proper methods on your object.
I'm often asked what the difference is between importing the namespace and declaring inheritance. In other words, what's the difference between the Imports and Inherits keywords? Well, the Imports statements only bring in the description of a set of functionality, it doesn't actually make use of it. As I said before, it's like setting a reference. The Inherits keyword actually takes part of the code referred to in the description and uses it. It's like Dim on steroids.
Now that I've defined my class, I need to define the methods and functions of the class. Again, this is very similar to the way it worked in Visual Basic 6.0, but with one new twist: you have to add a new attribute, <WebMethod()>, to every method that you want exposed to Web clients. This tells ASP.NET that the method in which it appears is to be exposed to clients as a Web Service. You've seen plenty of attributes in Visual Basic 6.0, such as public, private, and const. The .NET Framework and Visual Basic.NET use many more of them, which is why there's a new syntax for specifying them. Just as your class can contain public and private methods, so can it contain some methods that are exposed to Web clients and others that are not.
Finally, let's get down to the internals of the class's method. The handling of the date and time variables in my code may look a little strange to you; it was to me. Don't worry about it, it's just how Visual Basic.NET deals with dates and times.
Figure 4 TimeService.asmx in a Browser
Now let's access the Web Service from a client. ASP.NET contains good prefabricated support for this as well. If you fire up Microsoft Internet Explorer 5.5 and request the ASP.NET page I just wrote, you will see the page shown in Figure 4. ASP.NET detects the access to the Web Service page itself (as opposed to one of the methods within the service), and responds with a page of information about the service. This shows you all the methods that ASP.NET found in the target class (in this case, only GetTime), and provides a test capability for each method. Enter True or False to tell it whether to show the seconds digits or not, click Invoke, and the test page will call the specified method and return the results (if you entered TRUE), as shown in Figure 5. Note that the parameters are passed in the URL, and the results are returned as XML via the GET protocol. Also note that you must open the page in a way that goes through IIS, typing in a qualified UTL such as http://localhost/your virtual directory name/timeservice.asmx. If you simply double-click the page in the Explorer view of your hard disk, you'll bypass IIS and ASP.NET and simply receive the text of the .asmx page, which isn't what you're looking for.
Figure 5 Show Seconds
That's all I had to do to write my Web Service. It only took 13 lines, counting else and endif. How much easier does it get?
Self-description of Web Services: The SDL File
In order to allow programmers to develop client apps that use my Web Service, I need to provide the information that they need to have during their design and programming process. For example, a client of my Web Service would probably like to know the methods the Web Service exposes, the parameters required, and the protocols supported—something conceptually similar to the type library that a standard COM component would carry. The problem with type libraries, however, is that they are Microsoft COM-specific, and you may want non-Microsoft systems to be able to consume your Web Service as well. You may also want to be able to write descriptions of non-Microsoft services running on non-Microsoft systems, so that your Microsoft-built client applications can use these services. What you need is a universal method of describing a service. And you need it to be machine-readable, so that intelligent development environments can make use of it.
ASP.NET provides such a descriptive service. When it compiles a Web Service, ASP.NET can produce a file listing the protocols supported by the service and the methods provided and parameters required by that service. The file is encoded in XML and uses a vocabulary called Service Descriptor Language (SDL). The SDL file of a Web Service is sometimes known as its contract because it lists the things that the service is capable of doing and tells you how to ask for them.
You obtain the SDL file from ASP.NET by requesting the .asmx file with the characters ?SDL attached to the URL. For example, on my local machine, I obtained the SDL file for my sample Web Service by requesting the URL http://localhost/AspxTimeDemo/TimeService.asmx?SDL. Since I'm not exposing the sample service on an actual running server, I've provided this file in the sample code.
When you wrote COM components in Visual Basic 6.0 and Visual C++® 6.0, you sometimes wrote the component first and then wrote a type library to describe it. Other times you started with a type library describing an interface and wrote code that implemented it. SDL can work in both of these ways as well. You could write the code first, as I've done in this sample, in which case ASP.NET will generate an SDL file for interested clients. Alternatively, I could have written the SDL file first, describing what I'd like the service to do, and then use the utility program WebServiceUtil to generate a template file that implements the service that it describes (similar to the Implements keyword in Visual Basic 6.0).
The SDL file is somewhat complex, so I've extracted portions to discuss the principles (see Figure 6). The serviceDescription element is the root of the file. Everything inside it is part of the description of this Web Service. It contains an attribute giving the name of the Web Service, in this case, TimeService.
The root contains two interesting types of subelements. The first is a protocol description, which tells an interested client developer which protocols the service supports and how to encode the request and response data for that protocol. I've shown the one for the HTTP GET operation, since that's the easiest for me to visualize. Any person or development tool looking at the SDL will know that the service named TimeService can be accessed via this protocol. The full SDL file also contains entries saying that it can be accessed via the HTTP POST operation and also via the Simple Object Access Protocol (SOAP)—an HTTP/XML hybrid. I discuss HTTP POST and SOAP in the next section of this article. Looking inside the protocol description, you can see that the service contains a method called GetTime, which lives at the specified URL. The request requires a single parameter called ShowSeconds. The fact that it is used in an HTTP GET operation implies that it is passed in the form of a string. The response comes back in XML, again in the form of a string.
The second interesting piece in the SDL file is the W3C-standard <schema> element. This contains the abstract definition of the service, without regard for how it's accessed in any particular protocol or even where it lives. Think of this as an interface definition in a type library; it describes a set of methods, but not an implementation of them. You can see that my sample SDL file describes a function named GetTime, which requires a Boolean parameter called ShowSeconds. You can see that it also contains a separate description of the result of the GetTime method, which comes back in a string.
Writing Web Service Clients
Now let's look at what you have to do to write a client that accesses this service. I found writing the clients as easy as writing the service itself. The ASP.NET listener accepts three different ways of packaging the incoming request into HTTP: HTTP GET, HTTP POST, and SOAP. This is conceptually similar to an aircraft control tower speaking three dialects of English— say, American, British, and Strine (Australian). I'll look at all of these to examine how you would write a client that uses them.
Case 1: HTTP GET
The Web Service will accept a request through a simple HTTP GET request with the ShowSeconds parameter in the URL string. You saw the SDL file describing my Web Services support for this protocol in the previous section. A sample Web page providing access to this request is shown in Figure 7, and is provided as the file GetTimeThroughHttpGet.htm in this article's sample code. When the request reaches the server, ASP.NET parses the parameters from the URL string, creates the TimeService object, and calls the GetTime method.
Figure 7 Sample Web Page
Case 2: HTTP POST
The Web Service will accept a request through an HTTP POST request with the ShowSeconds parameter in an input control. The portion of the SDL file describing my Web Service's support for the HTTP POST operation is shown in Figure 8. The separate <request> and <response> elements indicate that making the request of the service, that is, posting the form, is a separate operation from receiving the response. This is how an HTTP POST operation normally works, so don't worry about it. The <form> element indicates that the POST operation requires a form containing an input control named ShowSeconds, which will carry this parameter. I used Microsoft FrontPage® 2000 to write a form that does this. A screen shot is shown in Figure 9, and the HTML code in Figure 10. The file is provided as GetTimeThroughHttpPost.htm. When the request reaches the server, ASP.NET creates the TimeService object, pulls the parameters from the form's controls, and calls the GetTime method.
Figure 9 Sending a Request via HTTP POST
Case 3: SOAP
The Web Service will accept an incoming call request via an HTTP POST request that has all of its information encoded in a SOAP packet. SOAP is an XML vocabulary that describes function calls and their parameters. Newcomers to SOAP will probably benefit from reading "Develop a Web Service: Up and Running with the Soap Toolkit for Visual Studio" in the August 2000 issue of MSDN Magazine, and A Young Person's Guide to the Simple Object Access Protocol in the March 2000 issue.
The portion of the SDL file describing my Web Service's support for the HTTP POST operation is shown in Figure 11. I've written a sample program, shown in Figure 12, that uses SOAP to call the GetTime method of the Web Service. It uses the Microsoft Internet transfer control to do the actual HTTP communication. Note that, unlike the previous two examples where the URL pointed at the method within the service, SOAP encoding is directed to the .asmx page containing all the methods of the Web Service. The SOAP packet sent to the server contains the name of the function and its parameters, encoded in XML according to an agreed-upon schema, as you can see in the top edit control. When the SOAP packet reaches the server, ASP.NET recognizes it, parses the method name and its parameters out of the packet, creates the object, and makes the call.
Figure 12 Soap Client
The XML encoding of the request packet varies somewhat from that used by the SOAP Toolkit Rob Caron discussed in his August 2000 article and the examples that come with it. SOAP and .NET are currently very much under development, so this type of divergence is only to be expected.
Case 4: Intelligent SOAP Proxy, Synchronous Operation
The SOAP example from the previous section is obviously quite tedious to write. It reminds me somewhat of manually writing an IDispatch client in COM, in the sense that there's an awful lot of boilerplate packaging that's critical to get correct (one character off and you're hosed), but which varies little from one method to the next. Just as C++ and Visual Basic-provided wrapper classes that took the pain out of accessing automation objects (many programmers using Visual Basic never even knew it was painful for the C++ geeks, and the rest either didn't care or actively approved), so does the .NET SDK provide the capability of generating wrapper classes that make writing a SOAP client for your Web Service a trivial operation.
webserviceutil /command:proxy /language:vb /path:timeservice.sdlThe Visual Basic-based proxy code is shown in Figure 13. It contains both synchronous and asynchronous versions of functions for getting the time from the Web Service. I'll discuss the synchronous version here, and the asynchronous version in the next section. The proxy class inherits from the base class System.Web.Services.Protocols.SoapClientProtocol (you're going to have these long names, so get used to them), which contains the actual code. The proxy class contains a property called Path, which the proxy inherits from the base class. This property specifies the URL of the server to which the call is directed. It contains a default value that it got from the original SDL file, but the sample program demonstrates how you can change it at runtime if you want. The client calls the named method on the proxy by calling the method Invoke, which, again, it has inherited from the base class. This method then creates a SOAP packet containing the method name and parameters (as shown in the previous section) and sends it to the server over HTTP. When the SOAP response packet comes back from the server, the base class parses out the return value and returns it to the proxy, which then returns it to the client.
Note that the attributes block in the function name (the characters between the angle brackets) contains information that tells the base class how to package the call, such as the names of methods and parameters. Visual Studio.NET makes extensive use of attributes as a way of passing information to the prefabricated functionality of system code. In earlier days, this would probably have been done through member variables of the base class where it would have been difficult to differentiate immutable runtime attributes from those that can change during program execution. The new arrangement is harder to mess up, which is generally a good thing.
The actual Visual Basic-based code for the client is shown in Figure 14, and the sample client app is shown in Figure 15. You can see that it's pretty trivial. The heavy lifting is done for you by the proxy class.
Figure 15 Client App
Case 5: Intelligent SOAP Proxy, Asynchronous Operation
The Internet is much more amorphous and nondeterministic than a single desktop. Like Disneyland, it can be a lot of fun, but it's almost always so crowded that you have no hope of enjoying it without a clever strategy for managing its chronic overload. Even the simplest call to a Web Service can easily take 5 or 10 seconds to complete, even on a good day with the wind at your back, and can go up to anything from there. You can't leave a user with a frozen interface for more than a second or two, if that.
You can't make blocking calls to a Web Service from the thread that handles your program's user interface. An industrial-strength app can't just call the synchronous proxy method from the form button's response function, as I did in the sample. You have to somehow make the call from another thread which can wait for the response without hanging the user interface, and at some later time retrieve the results from that thread and present them to the user.
Until now, most programmers have had to write their own infrastructure code to handle this situation. This type of code is notoriously tricky to get right in all cases, and the time that it took represented a dead loss in monetary terms. Now, however, since this situation is nearly universal, the intelligent proxy classes generated by WebServiceUtil is equipped with prefabricated code to handle it. Instead of making a call and blocking until it completes, your program can call one method that transmits the request data to the server and then returns immediately. Later, at some convenient time, your program can call another method to harvest the results returned from the server. This makes your life much, much easier because you don't have to write the scheduling code. And it works with any server, not just those that are written to support asynchronous access.
Figure 13 showed both the synchronous and asynchronous versions of the time service call. The proxy contains a method with the name Beginmethodname, in this case, BeginGetTime. Its parameter list begins with the parameters of the synchronous method, in this case, the ShowSeconds Boolean variable. It has two more parameters used in a callback case that is beyond the scope of this article. In the client code in Figure 14, I pass Nothing (the equivalent of null in Visual Basic) for both of them. This starts the communication chain, sending out the request, and returning immediately. The return value is an object of type IAsyncResult, which I will use in getting the result later. I can now go off and do whatever I want. When testing this in the lab, I inserted a long iterative loop in my .asmx page, just counting from one to a billion to simulate a long operation. You'll have to do that yourself if you want to test this.
At some later point, if you want to harvest the results of the operation, find out what the time actually was when the server sent it back to you. You do that by calling the Endmethodname method on the proxy, in this case, EndGetTime, passing the IAsyncResult that I got from the Begin method. That's how the infrastructure code knows which result to give back to you, as the client can have several outstanding requests at the same time. The end method harvests the result and returns it.
But how do you know when the operation is complete and that the results are ready for you to harvest? The IAsyncResult object contains a method used for just this purpose, called IsCompleted, which returns True or False. Your user interface thread can poll periodically to see if it's done. If you call EndGetTime before the operation is finished, it will block until the operation does in fact complete. While you probably don't want to do this from your main user interface thread, it may make sense to call it from a worker thread that has reached a point in its processing from which it can't proceed without the results of the Web Service call.
Web Service Support in Visual Studio.NET
The Web Service example I showed earlier demonstrated that writing Web Services does not depend on fancy programming environments. But since developer time is the second greatest constraint in software development, it makes sense to write your Web Services using tools that will help you crank them out faster. Since Notepad lacks such useful features as an integrated debugger, Visual Studio.NET is a better choice for writing Web Services. I'll show you how to write a service using the July 2000 PDC release of Visual Studio.NET.
When you select File | New | Project from the Visual Studio.NET main menu, it offers you the dialog box shown in Figure 16.
Figure 16 New Web Service
I selected Visual Basic Projects in the left pane, the Web Service icon in the right pane, entered a project name in the edit control, and clicked OK. Visual Studio.NET generated a new solution containing the files shown in Figure 17. By the way, the "solution" seems to hold one or more projects, so I really think project group would be a far more descriptive term.
Figure 17 Solution Explorer
The interesting programming takes place in the file WebService1.vb, where the code that implements your Web Service actually lives. The code for my sample Web Service is shown in Figure 18. I've written a bit more code in this one to demonstrate interesting features of the Web Service environment. It was much easier with Visual Studio.NET than with Notepad. If you look at the downloaded sample, you'll find that the figure omits a lot of administrative code placed in the class for the internal use of Visual Basic.NET (users of Visual J++® 6.0 will find it familiar). Comments in the code promise that it will be hidden in the final release, which will be helpful since it can be very confusing to read.
The wizard generates several other files, as you saw in Figure 17. The file Config.Web is an XML data file that contains various configuration options that tell ASP.NET how to handle your Web Service at runtime. For example, it contains the ASP.NET session timeout interval.
The file .disco is an XML data file that is used for controlling dynamic discovery of the Web Service by clients by collecting SDLs and schemas together. The default configuration, which you can see in the downloaded code, simply contains entries that tell ASP.NET to exclude certain subdirectories (generated in the publishing process) from the SDL file that it generates.
The .asmx file is the target for client requests. Unlike the previous example in which the code lived in this file, this example contains a callout to code that lives elsewhere, as shown here:
<%@ WebService Language="vb" Codebehind="WebService1.vb" Class="VS7DemoTimeService.WebService1" %>The Codebehind attribute tells it where to find the code that backs up the service.
The file globals.vb is where event handler functions live that handle project-wide events, such ApplicationStart and ApplicationEnd. You put your own code in these functions, and ASP.NET will call it when the specified event takes place. The file globals.asax is the connection between these files and ASP.NET.
When you build your project, Visual Studio.NET automatically publishes it to the Web server you specify (in this case, the system default). To test the Web Service, simply start the debugger from the Visual Studio.NET main menu. It opens a testing page similar to the one you saw in Figure 4, which is a very smooth way to handle things. You can set breakpoints in your Web Service and debug anything. The only snag I've found is that the Web Service doesn't handle bad user input well. For example, if the user puts in a bad value, say, he spells False incorrectly, ASP.NET rejects the incoming request before it hits your Web Service and you get the error shown in Figure 19.
Figure 19 Error Report
The original version of ASP contained a set of objects called the intrinsic objects, which represented the connection between an executing ASP script and the server on which it was running. This set included the Server, Application, Session, Request, and Response objects. An ASP script could access them directly; a COM object used by an ASP script had to jump through a few hoops to get them, but could also use them without too much trouble.
ASP.NET contains similar objects, generally updated and enhanced. They've also been repackaged to make them easier to use. The intrinsic objects are now part of the System.Web.Services.WebService base class. When you derive your service from that class, you inherit them for free (I told you that you were going to like inheritance).
The Server, Application, and Session objects are part of your Web Service class. They have been updated and enhanced somewhat. For example, the Server object now has an HtmlEncode method, to match its previously existing HtmlDecode. There is a new object called User, which you will use when performing security authorization (see the section on Security). The Request and Response objects have been moved inside a new intrinsic object called the Context (not to be confused with the ObjectContext used for committing and aborting transactions—its an unfortunate overloading of the nomenclature). You generally won't use these much, as their functions (accessing properties in the posted form) have been abstracted away by the Web Service infrastructure. I'll show how to use some of them in the next section.
Many, many trees have been senselessly murdered in the ongoing debate about stateful versus stateless objects. I won't even attempt to settle the question here. What I will do is to show you the options that are available for state management in a Web Service, which you can then use to implement whatever state behavior you decide you want.
Web Service objects in their natural state (groan) are stateless. That means that ASP.NET constructs a new instance of that object for each incoming call and destroys it at the end of the call. The result of one call is not available to the next call unless you go out of your way to make it so.
Sometimes this is what you want; sometimes it isn't. For my time service shown in this article, it probably is. What a client wants to show the seconds digit or not has no bearing on what the next client wants or should get. Each function call is sufficient unto itself; there's no reason to keep any state from one call to the next. In the classic sense of an object as a combination of data and the code that operates on the data, you might argue that there's no object here. The client is simply making a call that has no relation to anything else, and that's not an object, that's a function. Well, tough. I'm going to call it an object because it uses .NET object services, and you can't stop me.
When this behavior isn't what you want, an object can maintain state between one call and another. The object is still created and destroyed on demand, but it can use ASP.NET to hold data that it was working on in its previous life, as you might leave instructions in your will for your descendants. ASP.NET can maintain two types of state in a Web Service: application state and session state. The former is available to all parts of your Internet application; that is to say every session with every user in that object and all the others built into your solution. (See what a lame word solution is? Try substituting project group. Better, no?) The latter is available only to the session of the individual user currently connected to it. A session is a time-limited conceptual connection maintained by ASP.NET to make it easier for you to program behavior on a per-conversation basis. You use session state for remembering things like the items in a particular user's shopping cart, so you'll probably use it much more often than application-level state.
The ASP.NET system on which the Web Service is based provides a mechanism for maintaining and tracking state variables. The base class WebService from which your Web Service derives contains two collections for holding state, one called Application and one called Session. You can put data into them and take data out by accessing them through string names of items. The sample service shown in Figure 18 contains code that uses both types of state. I keep the total number of requests in the Application, and the number of requests for this particular user in the Session-level state.
Security in Web Services
Security is vital to any type of distributed programming, and discussions of it are often highly charged with emotion. I will attempt to outline the problems that arise in this area, and discuss how ASP.NET provides your Web Service with prefabricated functionality that allows you to get the level of security you need without too much trouble. But a full-scale discussion of security would take two or three full articles.
All Web Services care about security, whether you think they do or not. You might think that my sample time service doesn't, because I don't care who sees the timer. I still want to make sure that no one deletes it or hacks the code to make it subtract ten minutes from the current time and make all the callers late for their appointments. So I want read permission for anyone, and write permission only for a select few. IIS provides that basic level of security.
The security requirements of a Web Service are somewhat like those of a city hall. You have large numbers of people coming and going anonymously, accessing areas such as the tourism office that dispenses maps one at a time. But other areas of the same city hall, such as the floor containing the mayor's office, don't allow anyone in who cannot prove his identity (authentication) and who doesn't have business on the floor (authorization).
The first problem in security is authentication—who are you, and how do I know you really are that person? You can't figure out if this person or that one is allowed to access some resource until you know who they are. ASP.NET gets its authentication service from the underlying IIS layer. You use the existing IIS tools to mark particular pages as requiring authentication, as shown in Figure 20, clearing the Anonymous access box as shown. IIS will then require a user ID/password handshake before allowing the user to view the specified page. This requires that the user have an account accessible to the server. If the authentication succeeds, the ASP.NET script is run using the identity of the authenticated user.
Figure 20 Authentication Methods
When writing a .NET proxy-based client application (as shown earlier) the proxy base class contains properties called Username, Password, and Domain. Setting these properties, both strings, allows a client to easily pass them to a server that requires them.
Obviously, authentication requires extra network traffic, so you should only use it when needed. The city hall tourist office wouldn't be very useful if it required IDs and strip searches to obtain a map. My sample doesn't require authentication just to read the page, only to write it. In this case, the administrator leaves the Anonymous access box checked and specifies the identity of the account that any anonymous client will use for its access checks on the server. Generally, this account has a low level of privilege.
Once you've decided who the user is, you need to check before performing any sensitive action whether that person is or isn't allowed to do it. Sometimes this is handled by the back end operating system. For example, NTFS allows an administrator to set access permissions on files, and you hope that administrator won't allow anonymous users access to system configuration files. Other programs, such as Microsoft SQL Server™, do similar things.
It's usually best to perform authorization checks as early in the access process as possible, so that if they fail, you've wasted the minimum amount of processing time. Your Web Service object can perform its own authorization via the intrinsic object named User. This contains a method called IsCallerInRole, which will tell you whether the user is a member of an administrative group you've set up elsewhere. It is conceptually identical to the similar method seen in COM+. If you want to go deeper, you can use the Identity property of the User object. This returns an IIdentity interface, which tells you the user name and authentication mechanism used to verify that user name. You can write your own code that uses that information to decide if the user is allowed to do this or that.
Web Services will, at some future time, support Passport authentication and cookie-based authentication, which would make many lives easier. That is beyond the scope of this article.
All programmers would like to write programs that talk to each other over the Internet, no matter what type of system they're running on. You can accomplish this by settling on the lowest common denominator of XML and HTTP. The Web Services portion of the Microsoft .NET technology suite makes it easy for you to write your business logic in the form of .NET components and expose them to all Web clients that speak HTTP GET, HTTP POST, or SOAP. That's just about everyone in the world. An SDL file, also generated by the Web Services infrastructure, provides a machine-readable description of the functionality available through the Web Service. A proxy generator provides function-based access to a Web Service, making it easier to write client programs. Visual Studio.NET provides a productive environment for developing Web Services. The Web Service infrastructure provides several options for setting up the security required by a Web app. These tools allow you to write better Internet applications much more quickly—a good combination.
For related articles see:
Visual Studio.NET: Build Web Applications Faster and Easier Using Web Services and XML
P2P: Writing Peer-to-Peer Networked Apps with the Microsoft .NET Framework
David S. Platt is president and founder of Rolling Thunder Computing Inc. He teaches COM and COM+ at Harvard University and at companies all over the world. He publishes a free e-mail newsletter on COM+, available at http://www.rollthunder.com. David is the author of Understanding COM+ (Microsoft Press) and an upcoming book introducing the Microsoft .NET Platform, due in Spring 2001 (Microsoft Press).
|From the February 2001 issue of MSDN Magazine.|
Get it at your local newsstand, or better yet, subscribe.
|Back to top|