Creating a Web Service

There are two different coding techniques with which one can construct a web service: inline code and code-behind.

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:

Class

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.

CodeBehind

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.

Debug

Specifies whether the web service should be compiled with debug symbols.

Language

Specifies the language used for code written inline in the .asmx file.

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

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 following syntax:

MyVariable = MyDataset.Tables(tableindex).Rows(rowindex)(fieldindex)

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 the .asmx file in a browser to test the implementation. The result should be similar to Figure 4-3.

Browsing a web service

Figure 4-3. Browsing a web service

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:

  • 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.

Service description for Qotd.asmx

Figure 4-4. Service description for Qotd.asmx

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.

GetQotd documentation

Figure 4-5. GetQotd documentation

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.

Web Service Using Code-Behind

The following snippet is all that’s required for the .asmx file for our code-behind version of the Qotd web service (Qotd_cb.asmx):

<%@ 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.

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 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.

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.

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.

Tip

Web services that inherit from the WebService 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.

Browsing Qotd_cb.asmx

Figure 4-6. Browsing Qotd_cb.asmx

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.

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.

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.

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 the disco.exe command-line utility to create local WSDL files for all of their web services, as shown in the following code snippet:

disco http://localhost/aspnetian/Chapter_4/Qotd.disco

This line creates local WSDL files for both the Qotd and Qotd_cb web services.

UDDI

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.

Get ASP.NET in a Nutshell now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.