In this section, we describe how to develop a web service, first from the point of view of service providers and then of the consumers. Web services providers implement web services and advertise them so that the clients can discover and make use of the services. Because web services run on top of HTTP, there must be a web server application of some sort on the machine that hosts the web services. This web server application can be Microsoft Internet Information Services (IIS), Apache, or any other program that can understand and process the HTTP protocol. In our examples, we use Microsoft IIS, since that is the only web server currently supported by .NET.
We will be building a web service called PubsWS to let consumers get information from the sample Pubs database. All data access will be done through ADO.NET, so read Chapter 5 before attempting the examples.
Creating a web service is a three-step process:
Create a new asmx file for the web service. This must contain the <% webservice . . . %> directive, as well as the class that provides the web service implementation. To the web service clients, this asmx file is the entry point to your web service. You need to put this in a virtual directory that has the executescripts permission turned on.
Inherit from the WebService class of the System.Web.Services namespace. This allows the derived class to access all the normal ASP.NET objects exposed in the WebService base class such as Application, Session, Server, Request, and Response.[6] It is highly recommended that you specify a namespace for your web service before publishing it publicly because the default namespace, http://tempuri.org/, will not uniquely identify your web service from other web services. To do this, tag the WebService class with the Namespace attribute, specifying your own namespace.
[6] Access to the Request and Response objects can be done through the Context property of the WebService class.
Tag the public methods with WebMethod attributes to make web methodspublic methods of a distributed component that are accessible via the Web. You don't have to tag a method as WebMethod unless you want that method to be published as a web method.
The following C# code demonstrates a simple web service[7] that exposes four methods to Internet clients.
[7] For security reasons, the current release of ASP.NET runs as the account ASPNET. If you are using integrated security to access database resources, you must grant database access to the ASPNET account. You can also enable impersonation in the web.config or machine.config file:
<system.web> <identity impersonate="true" userName="." password=""/> </system.web>
If you set impersonate to true but leave UserName and password blank, the application will run as MachineName\IUSR_MachineName, so make sure to grant this user (or whatever userName you specify) database access.
We emphasize "Internet" because anyone that can access this asmx file on the web server can access these methods, as opposed to your COM component, which can be accessed only by COM clients:
<%@ WebService Language="C#" Class="PubsWS.PubsWS" %> namespace PubsWS { using System; using System.Data; using System.Data.OleDb; using System.Web; using System.Web.Services; [WebService(Namespace="http://Oreilly/DotNetEssentials/")] public class PubsWS : WebService { private static string m_sConnStr = "provider=sqloledb;server=(local);database=pubs; Integrated Security=SSPI"; [WebMethod(Description="Returns a DataSet containing all authors.")] public DataSet GetAuthors( ) { OleDbDataAdapter oDBAdapter; DataSet oDS; oDBAdapter = new OleDbDataAdapter("select * from authors", m_sConnStr); oDS = new DataSet( ); oDBAdapter.Fill(oDS, "Authors"); return oDS; } [WebMethod] public DataSet GetAuthor(string sSSN) { OleDbDataAdapter oDBAdapter; DataSet oDS; oDBAdapter = new OleDbDataAdapter( "select * from authors where au_id ='" + sSSN + "'", m_sConnStr); oDS = new DataSet( ); oDBAdapter.Fill(oDS, "SelectedAuthor"); return oDS; } [WebMethod(MessageName="GetBooksByAuthor", Description="Find books by author's SSN.")] public DataSet GetBooks(string sAuthorSSN) { OleDbDataAdapter oDBAdapter; DataSet oDS; oDBAdapter = new OleDbDataAdapter( "select * from titles inner join titleauthor on " + "titles.title_id=titleauthor.title_id " + "where au_id='" + sAuthorSSN + "'", m_sConnStr); oDS = new DataSet( ); oDBAdapter.Fill(oDS, "Books"); oDBAdapter = new OleDbDataAdapter("select * from authors " + "where au_id='" + sAuthorSSN + "'", m_sConnStr); oDBAdapter.Fill(oDS, "Author"); return oDS; } [WebMethod] public DataSet GetBooks( ) { OleDbDataAdapter oDBAdapter; DataSet oDS; oDBAdapter = new OleDbDataAdapter("select * from titles" , m_sConnStr); oDS = new DataSet( ); oDBAdapter.Fill(oDS, "Books"); return oDS; } } // End PubsWS }
If you are familiar with ASP, you may recognize the usage of the @ symbol in front of keyword WebService. This WebService directive specifies the language of the web service so that ASP.NET can compile the web service with the correct compiler. This directive also specifies the class that implements the web service so it can load the correct class and employ reflection to generate the WSDL for the web service.
Because PubsWS also uses ADO.NET's OLE DB provider for its data-access needs, we have to add a reference to System.Data and System.Data.OleDb, in addition to the System, System.Web, and System.Web.Services namespaces.
Class PubsWS inherits from WebService with the colon syntax that should be familiar to C++ or C# developers:
public class PubsWS : WebService
The four methods that are tagged with WebMethod attributes are GetAuthors( ), GetAuthor( ), GetBooks(string), and GetBooks( ). In C#, you can tag public methods with a WebMethod attribute using the [] syntax. In VB, you must use <>. For example, in VB, the second method would be declared as:
<WebMethod( )> Public Function GetAuthor(sSSN As String) As DataSet
By adding [WebMethod] in front of your public method, you make the public method callable from any Internet client. What goes on behind the scenes is that your public method is associated with an attribute, which is implemented as a WebMethodAttribute class. WebMethodAttribute has six properties:
Controls whether or not to buffer the method's response.
Specifies the length of time in seconds to keep the method response in cache; the default is not to hold the method response in cache (0 seconds). A cache hit is when an identical call with identical parameters is requested. The cached response is used to avoid re-processing.
Provides additional information about a particular web method.
Enables or disables session state. If you don't want to use session state for the web method, you should make sure that this flag is disabled so the web server doesn't have to generate and manage session IDs for each user accessing the method.
Distinguishes web methods with the same names. For example, if you have two different methods called GetBooks (one method retrieves all books while the other method retrieves only books written by a certain author) and you want to publish both of these methods as web methods, the system will have a problem trying to distinguish the two methods since their names are duplicated. You have to use the MessageName property to make sure all service signatures within the WSDL are unique. If the protocol is SOAP, MessageName is mapped to the SOAPAction request header and nested within the soap:Body element. For HTTP GET and HTTP POST, it is the PathInfo portion of the URI (as in http://localhost//PubsWS/PubsWS.asmx/GetBooksByAuthor).
Can be one of five modes: Disabled, NotSupported, Supported, Required, and RequiresNew. Even though there are five modes, web methods can only participate as the root object in a transaction. This means both Required and RequiresNew result in a new transaction being created for the web method. The Disabled, NotSupported, and Supported settings result in no transaction being used for the web method. The TransactionOption property of a web method is set to Disabled by default.
To set up these properties, pass the property name and its value as a name = value pair:
[WebMethod(Description="Returns a DataSet containing all authors.")] public DataSet GetAuthors( )
You can separate multiple properties with a comma:
[WebMethod(MessageName="GetBooksByAuthor", Description="Find books by author's SSN.")] public DataSet GetBooks(string sAuthorSSN)
If you set up your web services from scratch, you should also need to provide the configuration file (web.config) in the same directory as your asmx file. This configuration file allows you to control various application settings about the virtual directory. Here, we set the authentication mode to None to make our web services development and testing a little easier. When you release your web services to the public, you should change this setting to Windows, Forms, or Passport instead of None:
<configuration> <system.web> <authentication mode="None" /> </system.web> </configuration>
The following list shows the different modes of authentication:
Basic Forms authentication is where unauthenticated requests are redirected to a login form.
Authentication is performed by IIS in one of three ways: basic, digest, or Integrated Windows Authentication.
Unauthenticated requests to the resource are redirected to Microsoft's centralized authentication service. When authenticated, a token is passed back and used by subsequent requests.
In v1.1 of .NET Framework, for security reasons, HttpGet and HttpPost protocol are disabled by default. To override the default and enable or disable any particular protocols, the web.config can be modified as the following:
<configuration> . . . <webServices> <protocols> <add name="HttpGet" /> <add name="HttpPost" /> </protocols> </webServices> . . . </configuration>
After creating the web service, you can provide the supporting files to help in the discovery of the service. The static discovery disco file is:[8]
[8] This code snippet assumes the virtual directory you set up is /PubsWS on your local web server.
<?xml version="1.0" ?> <disco:discovery xmlns:disco="http://schemas.xmlsoap.org/disco/" xmlns:scl="http://schemas.xmlsoap.org/disco/scl/"> <scl:contractRef ref="http://localhost/PubsWS/PubsWS.asmx?WSDL"/> </disco:discovery>