a single-file web service is quite simple. All
that’s required is to create a new file, add an
@ WebService directive and a class containing the
implementation for the methods you want to expose, and decorate the
methods to be exposed with the
supports the following attributes:
Specifies the name of the class containing the implementation of the web service. This attribute is necessary to allow the ASP.NET runtime to locate the compiled class at runtime.
Specifies the name of a code-behind file that contains the class that implements the web service. This attribute is used by Visual Studio .NET when building the project containing the code-behind class.
Specifies whether the web service should be compiled with debug symbols.
Specifies the language used for code written inline in the
To demonstrate the creation of a web service, look at Example 4-1, which implements a simple “Quote of the Day” web service.
Example 4-1. Quote of the day application (Qotd.asmx)
<%@ WebService Language="VB" Class="Qotd" %> Imports System Imports System.Data Imports System.Web Imports System.Web.Services Public Class Qotd <WebMethod( )> _ Public Function GetQotd( ) As String Dim QuoteDS As New DataSet( ) Dim Context As HttpContext = HttpContext.Current( ) Dim QuoteXML As String = Context.Server.MapPath("qotd.xml") Dim QuoteCount As Integer Dim QuoteToReturn As Integer Dim Randomizer As New Random( ) QuoteDS.ReadXml(QuoteXML) QuoteCount = QuoteDS.Tables(0).Rows.Count QuoteToReturn = Randomizer.Next(0, QuoteCount) Return QuoteDS.Tables(0).Rows(QuoteToReturn)(0) & _ "<br /><br />" & QuoteDS.Tables(0).Rows(QuoteToReturn)(1) End Function End Class
WebService directive in Example 4-1 specifies Visual Basic .NET as the language
used in the web service and specifies that the web
service’s implementation is contained in a class
Qotd. The next four lines import several
namespaces to save the effort of typing in the namespace name each
time a member is used in the code.
Next comes the class definition for the
class. This class contains a single
, which returns a string containing a
quote and the name of its author, separated by two HTML line breaks.
Note that this definition assumes that the consumer of the web
service will display the results as HTML. In a later example,
we’ll provide a more flexible implementation.
Within the method, you create an ADO.NET dataset (see Chapter 7 for more information on ADO.NET) and use the
method of the
class to read in the stored quotes from a
simple XML file. The contents of this file are shown in Example 4-2. Once the data is loaded into the dataset, you
check the Count property to determine how many records exist and then
use an instance of the
class to return a random number
from 0 to the record count. This number is then used to retrieve the
first and second values (which also happen to be the only values) of
the desired row, as shown in the following snippet, and return it to
the caller of the method:
Return QuoteDS.Tables(0).Rows(QuoteToReturn)(0) & _ "<br /><br />" & QuoteDS.Tables(0).Rows(QuoteToReturn)(1)
Note that since collections in .NET are zero-based, Tables(0) refers to the first table in the Tables collection of the dataset (in this case, the only table). You can access the value of a particular field in a particular row in a specific table by using the following syntax:
MyVariable = MyDataset.Tables(
Example 4-2. Qotd.xml
<Quotes> <Quote> <QuoteText>Never give in--never, never, never, never, in nothing great or small, large or petty, never give in except to convictions of honour and good sense. Never yield to force; never yield to the apparently overwhelming might of the enemy.</QuoteText> <QuoteAuthor>Winston Churchill</QuoteAuthor> </Quote> <Quote> <QuoteText>We shall fight on the beaches. We shall fight on the landing grounds. We shall fight in the fields, and in the streets, we shall fight in the hills. We shall never surrender!</QuoteText> <QuoteAuthor>Winston Churchill</QuoteAuthor> </Quote> <Quote> <QuoteText>An appeaser is one who feeds a crocodile-hoping it will eat him last.</QuoteText> <QuoteAuthor>Winston Churchill</QuoteAuthor> </Quote> <Quote> <QuoteText>We shape our buildings: thereafter they shape us.</QuoteText> <QuoteAuthor>Winston Churchill</QuoteAuthor> </Quote> <Quote> <QuoteText>Science without religion is lame, religion without science is blind.</QuoteText> <QuoteAuthor>Albert Einstein</QuoteAuthor> </Quote> <Quote> <QuoteText>As far as the laws of mathematics refer to reality, they are not certain, and as far as they are certain, they do not refer to reality.</QuoteText> <QuoteAuthor>Albert Einstein</QuoteAuthor> </Quote> <Quote> <QuoteText>If A equals success, then the formula is A equals X plus Y plus Z. X is work. Y is play. Z is keep your mouth shut.</QuoteText> <QuoteAuthor>Albert Einstein</QuoteAuthor> </Quote> <Quote> <QuoteText>When a man sits with a pretty girl for an hour, it seems like a minute. But let him sit on a hot stove for a minute-and it's longer than any hour. That's relativity.</QuoteText> <QuoteAuthor>Albert Einstein</QuoteAuthor> </Quote> </Quotes>
Once you’ve added the code in Example 4-1 to a file, saved it with the
.asmx extension, and created a file called
Qotd.xml with the text in Example 4-2 in the same virtual directory, you can open
.asmx file in a browser to
test the implementation. The result should be similar to Figure 4-3.
The main documentation page displayed in Figure 4-3
is generated automatically by the ASP.NET runtime whenever a web
asmx file) is called
from a browser rather than by a SOAP request. You should note three
things about the page in Figure 4-3:
The link to the service description. Accessing this link displays the WSDL contract (see Figure 4-4), which describes the methods exposed by the web service (in much the same way as an IDL file describes a COM object). This contract is also used by .NET clients to generate proxy classes for consuming the web service. This topic is discussed in more detail later in the chapter.
The link that provides access to a documentation page for the GetQotd method, which is shown in Figure 4-5. If the web service exposed multiple methods, the main documentation page would provide a link for each.
The main documentation page also displays a recommendation about the default namespace for the web service. This recommendation refers to the XML namespace, which if not specified, defaults to http://tempuri.org and should not be confused with the .NET namespaces. A later example demonstrates how to set the default namespace to a unique URL.
As shown in Figure 4-5, the documentation page for the GetQotd method provides an Invoke button that allows you to test the web service method and that provides documentation on creating SOAP, HTTP GET, and HTTP POST requests for the selected method. In this case, HTTP GET and POST are not shown.
If you click the Invoke button, a new browser window should open, displaying XML text similar to the following snippet. (Note that the quotation and author may vary, since they are selected randomly.)
<?xml version="1.0" encoding="utf-8" ?> <string xmlns="http://tempuri.org/">We shape our buildings: thereafter they shape us.<br><br>Winston Churchill</string>
Because the GetQotd method returns a string containing HTML
<br /> tags), it will
automatically display the quote and author on separate lines if shown
in a browser. But what if a consumer of the web service wants to
apply a different format to the quote than the author?
With this implementation, they’re out of luck,
unless they are willing to parse out the two parts and apply the
formatting individually that way. To address this issue, look at a
modified version of the
Qotd web service that uses
a code-behind class for its implementation.
<%@ WebService Language="VB" Class="aspnetian.Qotd_cb" %>
Note that instead of providing the class name,
Qotd_cb, we’ve also added a
“aspnetian,” to reduce the
likelihood of naming conflicts. Example 4-3, which
contains the code-behind class that implements the web service,
defines this namespace.
Example 4-3. Qotd_cb.vb
Imports System Imports System.Data Imports System.Web Imports System.Web.Services Namespace aspnetian <WebService(Namespace:="http://www.aspnetian.com/webservices/")> _ Public Class Qotd_cb Inherits WebService <WebMethod( )> _ Public Function GetQotd( ) As String Dim QuoteDS As New DataSet( ) Dim QuoteXML As String = Server.MapPath("qotd.xml") Dim QuoteCount As Integer Dim QuoteNumber As Integer Dim Randomizer As New Random( ) QuoteDS.ReadXml(QuoteXML) QuoteCount = QuoteDS.Tables(0).Rows.Count QuoteNumber = Randomizer.Next(0, QuoteCount) Return QuoteDS.Tables(0).Rows(QuoteNumber)(0) & "<br /><br />" _ & QuoteDS.Tables(0).Rows(QuoteNumber)(1) End Function <WebMethod( )> _ Public Function GetQuoteNumber( ) As Integer Dim QuoteDS As New DataSet( ) Dim QuoteXML As String = Server.MapPath("qotd.xml") Dim QuoteCount As Integer Dim Randomizer As New Random( ) QuoteDS.ReadXml(QuoteXML) QuoteCount = QuoteDS.Tables(0).Rows.Count Return Randomizer.Next(0, QuoteCount) End Function <WebMethod( )> _ Public Function GetQuote(QuoteNumber As Integer) As String Dim QuoteDS As New DataSet( ) Dim QuoteXML As String = Server.MapPath("qotd.xml") Dim QuoteCount As Integer Dim QuoteToReturn As String QuoteDS.ReadXml(QuoteXML) QuoteToReturn = QuoteDS.Tables(0).Rows(QuoteNumber)(0) Return QuoteToReturn End Function <WebMethod( )> _ Public Function GetAuthor(QuoteNumber As Integer) As String Dim QuoteDS As New DataSet( ) Dim QuoteXML As String = Server.MapPath("qotd.xml") Dim QuoteCount As Integer Dim AuthorToReturn As String QuoteDS.ReadXml(QuoteXML) AuthorToReturn = QuoteDS.Tables(0).Rows(QuoteNumber)(1) Return AuthorToReturn End Function End Class End Namespace
In addition to wrapping the class declaration in a namespace
declaration, this example adds a new attribute,
WebService, and several new methods. The
attribute is added at the class level
so we can specify the default namespace (XML namespace) for the web
service. This namespace needs to be a value unique to your web
service. In the example, the namespace is http://www.aspnetian.com/webservices/; for
your own web services, you should use your own unique value. You may
want to substitute a URL that you control, as doing so will assure
you that web services created by others will not use the same value.
The added methods are GetQuoteNumber, GetQuote, and GetAuthor. These methods demonstrate that even though web service requests are sent as XML text, the input and output parameters of web service methods are still strongly typed. These methods address the potential formatting issue discussed previously by allowing clients to retrieve a quote and its author separately in order to accommodate different formatting for each. To ensure that the matching author for the quote is retrieved, the client would first call GetQuoteNumber to retrieve a randomly generated quote number, and then call GetQuote and/or GetAuthor, passing in the received quote number. This provides the client more flexibility, but does not require the web service to keep track of which quote number was sent to a given client.
An important difference between the single-file web service and the
code-behind implementation is that for the code-behind version, you
must compile the code-behind class into an assembly manually and
place it in the
before the web service will work. Note that this step is automatic
when you build a web service project in Visual Studio .NET. If
you’re writing code by hand, this step can be
accomplished by using a DOS batch file containing the commands shown
in the following snippet:
vbc /t:library /r:System.Web.dll /r:System.dll /r:System.Web.Services.dll /r:System.Xml.dll /r:System.Data.dll /out:bin\qotd_cb.dll qotd_cb.vb pause
Note that all command-line options for the
vbc.exe compiler should be part of a single
pause command allows you to see any
warnings or errors generated during compilation before the command
In Example 4-1, the Current property of the
class is used to get a reference to the
Context object for the current request. Getting this reference is
necessary to access to the Server intrinsic object so that we can
call its MapPath method to get the local path to the XML file used to
store the quotes. However, as you add more methods that use the XML
file, you end up with redundant calls to HttpContext.Current.
For better readability and maintainability, you can eliminate these
calls by having the web service class inherit from
System.Web.Services.WebService. Inheriting from
automatically provides access
to the Server, Session, and Application intrinsic objects, as well as
HttpContext instance for the current
request and the User object representing the authenticated user. In
the case of Example 4-3, inheriting from
WebService eliminates the calls to
Web services that inherit from the
class have access to the ASP.NET Session object. However, you should
carefully consider whether your application will benefit from storing
state information in the Session collection before using
it -- particularly if your application may need to scale to more
than one web server. ASP.NET now provides out-of-process Session
state options that can be used in web farm situations. Unfortunately,
because these solutions require, at best, an out-of-process call (and
at worst, a cross-machine call), using them results in a significant
performance penalty. Regardless of your decision, you should always
load-test your application to ensure that it will meet your
performance and scalability needs.
Figure 4-6 shows the main documentation page for
the code-behind version of the
Qotd web service.
Note that the main documentation page contains links for each new
method exposed by the web service. Also note that the page no longer
displays the namespace warning/recommendation, since we set the
default namespace in this version.
You’ve written a web service and you tested it by
.asmx file in a
browser and invoking the methods. What’s next?
Unless your web service will be consumed only by yourself or by
someone with whom you have regular communication, you need to publish
or advertise your web service in some way. Potential clients also
need to locate your web service to use it.
Publishing a web service can be accomplished in either of two ways: through a discovery document or by registering the web service with a UDDI directory.
A discovery document is a file with the
references to the WDSL contracts for web services you want to
publish, references to documentation for your web services, and/or
references to other discovery documents.
You can publish a discovery document on your web server and provide
clients with a link to or a URL for the discovery document. Clients
can then use the
command-line utility to generate WSDL contracts locally for creating
proxy classes to communicate with the web service. Example 4-4 shows the format of a discovery document for
Qotd web service.
Example 4-4. Qotd.disco
<?xml version="1.0"?> <discovery xmlns="http://schemas.xmlsoap.org/disco/"> <contractRef ref="http://localhost/aspnetian/Chapter_4/Qotd.asmx?wsdl" docRef="http://localhost/aspnetian/Chapter_4/Qotd.asmx" xmlns="http://schemas.xmlsoap.org/disco/scl/" /> <contractRef ref="http://localhost/aspnetian/Chapter_4/Qotd_cb.asmx?wsdl" docRef="http://localhost/aspnetian/Chapter_4/Qotd_cb.asmx" xmlns="http://schemas.xmlsoap.org/disco/scl/" /> </discovery>
Once clients know the location of the discovery file, they can use
utility to create local WSDL files for all of their web services, as
shown in the following code snippet:
This line creates local WSDL files for both the
Qotd_cb web services.
The other method used for publishing and locating web services is UDDI. Still in the process of maturing, UDDI works on the principle of providing multiple replicated directories in which public web services are registered. The UDDI web site (http://www.uddi.com) contains a list of the participating directory sites from which clients or providers of web services can choose. Providers of web services give relevant information, such as the type of web service, an appropriate category (such as Construction or Financial and Insurance.), and most importantly, the URL for the application’s WSDL file. Potential clients can search the UDDI directory for web services that match their needs and then locate and consume them via their WSDL contract.