4.3 Creating a Web Service
4.3.1 Web Service with Inline Code
Creating 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 WebMethod attribute. The @ WebService directive supports the following attributes:
To demonstrate the creation of a web service, look at Example 4-1, which implements a simple "Quote of the Day" web service.
<%@ 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
The 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 named 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 Qotd class. This class contains a single function, GetQotd, 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 ReadXml method of the DataSet 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 Random 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 syntax:
My Variable = MyDataset.Tables(tableindex).Rows(rowindex)(fieldindex)
<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 the .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 service (.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:
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 formatting (the <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.
4.3.2 Web Service Using Code-Behind
<%@ WebService Language="VB" Class="aspnetian.Qotd_cb" %>
Note that instead of providing the class name, Qotd_cb, we've also added a namespace name, "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.
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 WebService 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. If you are developing your web service with Visual Basic .NET in Visual Studio .NET 2003, the Namespace attribute will automatically be set to a URL consisting of the value http://tempuri.org/, plus the project name, plus the name of the .asmx file (e.g., http://tempuri.org/myproject/mywebservice).
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 bin directory 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 command. The pause command allows you to see any warnings or errors generated during compilation before the command window is closed.
4.3.3 Inheriting from WebService
In Example 4-1, the Current property of the HttpContext 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 WebService automatically provides access to the Server, Session, and Application intrinsic objects, as well as to the 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 HttpContext.Current entirely.
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 opening the .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.
4.3.4 Discovery Documents
A discovery document is a file with the extension .disco that contains 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.
4.3.5 Publishing and Locating Web Services
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 disco.exe .NET 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 the Qotd web service.
<?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 the disco.exe command-line 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 and Qotd_cb web services.
The other method used for publishing and locating web services is UDDI. Still maturing, UDDI works by providing multiple replicated directories in which public web services can be 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.