2002-09-12 11:10
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.
Solution Architecture
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.
The tool
that does this is a command-line utility called WebServiceUtil that comes with
the .NET SDK. It's also available from Visual Studio, as described in the
September 2000 issue of MSDN Magazine (see "Visual Studio .NET: Build Web Apps
Faster and Easier Using Web Services and XML"). This program reads the
description of the Web Service from an SDL file and generates a proxy for
accessing them from the language you specify. It currently supports Visual Basic
.NET, C#, and JavaScript, but not C++. I copied the SDL file to a local
directory and ran WebServiceUtil with the command line:
webserviceutil /command:proxy /language:vb /path:timeservice.sdl
The 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
Intrinsic Objects
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.
State Management
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.
Conclusion
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. |