Creating a Web Service

In ASP.NET, the complexities of SOAP messaging and HTTP transport are abstracted from developers, so creating XML-based Web services is actually quite simple. This abstraction is enabled through attributes that tell the common language runtime (CLR) to treat a specified class and its methods as Web services. Based on these attributes, the CLR provides all of the plumbing necessary to expose the class and methods as Web services, as well as providing a contract that clients can use to determine how to call the Web service and the return types to expect from it.

Declaring a Web Service

Web services in ASP.NET are defined in files with the .asmx file extension. The actual code for an ASP.NET Web service can reside either in the .asmx file or in a precompiled class. Either way, the Web service is declared in the .asmx file by adding the @ WebService directive to the file. The syntax for declaring a Web service in which the class will be defined within the .asmx files is

<%@ WebService Language="C#" Class="ClassName" %>

ClassName is the name of the class that contains the Web service methods. The syntax for declaring a Web service in which the code that defines the class and methods resides in a compiled class is

<%@ WebService Class="NamespaceName.ClassName,AssemblyName" %>

NamespaceName is the name of the namespace in which the class for the Web service resides, ClassName is the name of the class that contains the Web service methods, and AssemblyName is the name of the assembly that contains the compiled code for the Web service. This assembly should reside in the bin directory of the Web application in which the .asmx file resides. The NamespaceName portion of the Class attribute is optional if you have not specified a namespace for your class, but it is good practice to always specify a namespace for your classes. The AssemblyName portion of the Class attribute is also optional, since ASP.NET will search the bin directory for the assembly if no assembly name is provided. However, you should always provide an assembly name to avoid the performance impact of doing a search the first time the Web service is accessed.

Note

Unlike using code-behind with ASP.NET Web Forms, you cannot use the src attribute with a Web service. This means that you must precompile the code-behind class that is to be used as the basis for the Web service, either manually using the command-line compiler or by building a Microsoft Visual Studio .NET Web Service application.

Important

Visual Studio .NET provides a default framework for any Web service you create inside its IDE. The following section describes the specifics of creating a Web service class. In the section that follows, you’ll create examples using Visual Studio .NET, and therefore many of the tasks required to get a basic Web service class will be in place with the default class created in the IDE. Nonetheless, it is important to understand the base classes and attributes used to create a Web service.

Creating the Web Service Class

Each .asmx file is associated with a single class, either inline in the .asmx file or in a code-behind class (the default in Visual Studio .NET). Either way, the syntax of a Web service class is as follows:

Inheriting from WebService

In this syntax example, an Imports statement imports the System.Web.Services namespace. This namespace contains the classes and attributes that provide ASP.NET’s built-in support for XML-based Web services. In this case, the class we care about is WebService, which is inherited in this example using the Inherits keyword.

Inheriting from the WebService class is not required for a class to function as a Web service, but the WebService class provides useful plumbing for Web services. This includes access to Application, Session, and other ASP.NET objects, including the Context object, which provides access to information about the HTTP request that invokes the Web service. Any Web service you create using Visual Studio .NET will use the WebService class as the base class.

Using the <WebService()> Attribute to Specify XML Namespace

XML-based Web services use XML namespaces to uniquely identify the endpoint or listener to which a client sends requests. By default, ASP.NET sets the XML namespace for a Web service to http://tempuri.org/projectname/classname. If you plan to make your Web service available publicly over the Internet, you might want to change the default namespace to avoid potential conflicts with other XML-based Web services using the same names as your Web service, and which also might use the default namespace. Using a domain name that you control can help ensure uniqueness.

You can modify the default namespace for a Web service class by adding the WebService metadata attribute to the class definition and setting its Namespace property to the desired value for the new namespace. This value can be any unique URI that you control, such as your organization’s domain name. The namespace is used only for identification—the URI specified is not used in any other way, and need not be a valid, accessible Web site.

In Microsoft Visual C# .NET, metadata attributes use the syntax [AttributeName(PropertyName=value)] and are placed on the same line as the entity (class, method, and so on) that they modify:

Prior to this chapter, the term attributes referred to the key/value pairs added to HTML or ASP.NET server control tags to define specific properties of those entities or controls. This chapter introduces the concept of metadata attributes.

ASP.NET and the Microsoft .NET Framework make extensive use of attributes to provide programmatic plumbing for classes, as well as to add descriptive information to managed assemblies. You can also use your own attributes (by creating classes derived from System.Attribute) to add your own descriptive information to your classes.

Attributes in ASP.NET Web services, in addition to providing access to the XML namespace used for the Web service, are used to tell ASP.NET that a given method is to be exposed as a WebMethod. ASP.NET then provides all of the runtime functionality necessary for clients of the Web service to access the WebMethod without requiring the programmer to write additional code.

You can set multiple properties in an attribute declaration and separate them by commas. Placing each property definition on a separate line can substantially improve code readability, particularly if you are using many properties in an attribute declaration.

Using the <WebMethod()> Attribute to Expose Methods

Once you’ve declared the class that implements the Web service, you will need to add one or more methods that provide functionality for the Web service. Each method will use the WebMethod metadata attribute to declare the method as one for which ASP.NET should provide the necessary plumbing to expose it to clients.

To add the WebMethod attribute to a Visual C# .NET method, use the following syntax:

[WebMethod() public <ReturnType> MethodName( // method cod }

MethodName is the name of the method to be exposed by the Web service; ReturnType is the type of the value (if any) to be returned by the method. Even if your method does not return a value that is a result of a calculation or other action, it is usually a good idea to at least return a value that indicates the success or failure of the method.

<WebMethod()> Attribute Properties

Like the WebService attribute, the WebMethod attribute has properties that you can use to alter the behavior of the Web method. Table 11-1 describes these properties.

Table 11-1: WebMethod Properties

Property

Description

BufferResponse

Determines whether output from the method is buffered in memory before being sent to the client. The default is True.

CacheDuration

Allows the output of a Web service method to be cached on the server for the specified duration, in seconds. The default is 0, which turns off caching for the method.

Description

Sets a text description of the Web service method. This description is shown in the documentation pages autogenerated by ASP.NET, and it’s included in the Web Service Description Language (WSDL) contract for the Web service.

EnableSession

Determines whether session support is enabled for the Web service method. Default is False. Enabling session state requires the Web service class to inherit from WebService. Note that enabling session state for a Web service can have a negative impact on performance.

MessageName

Allows methods to be given alias names. This can be useful when you have an overloaded method, in which the same method name has multiple implementations that accept different input parameters. In this case, you can supply a different MessageName for each overloaded method version, allowing you to uniquely identify each one. The default is the method name.

TransactionOption

Allows adding support for COM+ transactions in a Web service method. Allowable values are Disabled, NotSupported, Supported, Required, and RequiresNew. The default is Disabled. Note that this property requires the use of an @ Assembly directive to load the System.EnterpriseServices assembly into the Web service application.

Create a HelloWorld Web service

Create a new project in Visual Studio .NET. Unlike previous projects, this project should be an ASP.NET Web Service rather than an ASP.NET Web Application. Type the name of the project as Chapter_11, and click OK. The resulting screen should show the default Web Service, Service1.asmx.cs, in design view, as shown in the following illustration.

Switch to code view. You can click on the link provided in design view to switch to code view, or use the standard Visual Studio .NET key, F7. However you switch to code view, the screen should look similar to the following illustration:

Unlike Web Applications, in which a great deal of the work of building the page is done in design view, Web services are generally modified using code directly.

Toward the bottom of the file, locate a comment titled WEB SERVICE EXAMPLE. The code will look similar to the following:

Remove the comments from the beginning of each line from [WebMethod()] to the closing curly brace for the function. Rather than leave this example exactly as it is created by Visual Studio .NET, add a parameter to HelloWorld, and append the parameter to the value returned. The finished method should look like the following code:

[WebMethod public string HelloWorld(string name return "Hello World, from " + name }

Save the file, and then build the project.

Right-click on Service1.asmx in Solution Explorer. Select Browse With from the drop-down menu. Select Microsoft Internet Explorer from the browser list, and then click Browse. The resulting screen should look similar to the following illustration.

One thing that stands out on the illustration is the warning that the Web service is using the http://tempuri.org/namespace. There is a recommendation to change the namespace before the Web service is made public. Although it is unlikely this Web service will ever be made public, let's fix this now. Close the browser and return to the source code.

Add a line directly below the closing summary tag just above the top of the class declaration. Note that if you press Enter at the end of the line with the closing summary tag, you should see that Visual Studio .NET will insert three slashes (as a convenience, presuming you are adding more comments). Delete the three slashes if they appear. On the line just below the closing summary tag, add the following (you can substitute a different namespace value, if you like, as long as it is unique):

[WebService(Namespace="http://www.aspnetsbs.com/webservices/")]

Save the open files, and build the project again.

Browse the web service again using Internet Explorer, as described in step 6. The result should look similar to the following illustration:

There are two links on this page. The first is Service Description. Click this link, and a screen similar to the following illustration will appear:

You can scroll through the entire document and see the complete Web Services Description Language (WSDL) contract for the Web service method. Click the Back button on the browser and select the other link on the page, HelloWorld. The screen should look similar to the following illustration.

Enter your name in the text box provided for the name, and then click the Invoke button. The resulting screen should look similar to the following illustration, which shows the XML output returned by the Web service.

The complete source for the .asmx file and the code-behind file can be found in the practice files for the book.

Important

The example Web services in this chapter all use the XML namespace http://www.aspnetsbs.com/webservices/. As with the default http://tempuri.org/ namespace, if you intend to make your XML-based Web services publicly available over the Internet, you should not use http://www.aspnetsbs.com/webservices/ as the namespace for your Web service. Instead, use a URL that you control, and one that will allow you to be sure that your Web service can be uniquely identified.

Using Primitive Data Types

The HelloWorld example takes a single string as an input parameter, and returns a value of type String. But XML-based Web services are not limited to strings and can accept input parameters and return values of most types.

XML-based Web services in ASP.NET use the XML Schema Definition (XSD) draft specification to specify the datatypes that are supported by ASP.NET Web services. This includes primitive data types such as string, int, boolean, long, and so on, as well as more complex types such as classes or structs.

The definition for a Web service method that adds two numbers and returns the result would look like the following:

Accessing Data in a Web Service

In addition to primitive types, classes, and structs, ASP.NET Web services can also return or accept ADO.NET datasets as input parameters. This is possible because the DataSet class has built-in support for being serialized as XML, which can be easily sent over an HTTP connection.

Return data from a Web service

In Visual Studio .NET, open the Chapter_11 project if it is not already open.

Create a new Web service. Right-click on the root of the project, then select Add from the drop-down menu, and then select Add Web Service. In the resulting dialog box, type the name of the file as AuthorsService.asmx, and click Open. When the design view of the new Web service appears, click on the link provided on the design surface to switch to code view.

At the top of AuthorsService.asmx.cs, add the following line:

using System.Data.SqlClient;

Just above the class declaration for the AuthorsService class, add the following line. If you press Enter at the end of the line with the closing Summary tag, Visual Studio .NET will add a new line with three slashes. Delete these before adding the line.

[WebService(Namespace="http://www.aspnetsbs.com/webservices/")]

Scroll to the bottom of the code file, and uncomment the commented HelloWorld method. Change the name of the method to GetAuthors, and the return type of the method from string to DataSet. Modify the WebMethod attribute just before the GetAuthors method, adding the description so that it looks like the following line:

[WebMethod(Description="Returns the Pubs Authors table.")]

Modify the GetAuthors method so that it looks like the following code:

Much of the database access code should look very familiar from previous examples. Note that the connection is neither opened nor closed in this example. This is because the SqlDataAdapter class takes care of opening, and closing the connection automatically, using the connection object passed to its constructor.

Save the file and build the application.

Browse the Web service with Internet Explorer. The screen should look similar to the illustration on the next page. (Note that the description specified is the same as the Description property of the WebMethod attribute.)

Click GetAuthors. The resulting page will look similar to the last example page that allowed you to invoke the Web service, except in this case, there will be an Invoke button, but no text box (because there are no parameters). Click Invoke, and a window similar to the following illustration will appear.

The schema for the resulting data is at the top of the result from invoking the Web service. Scroll down in the browser window, and you will see the actual data, as shown in the following illustration.

Advertising a Web Service

Once you’ve created a Web service using the techniques in the previous examples, the Web service is ready to be accessed by clients. The challenge at this point is how to give clients the information necessary to allow them to access your Web service.

In an intranet-based scenario, this is relatively simple. You can provide potential internal clients with the URL to your Web service directly, either by e-mailing it to those who need it or by creating a centrally located Web page that users within your organization can browse to find the appropriate URL. In an Internet-based scenario, however, you might not know who your potential clients are. This makes it much more difficult to ensure that they can find and use your Web services.

You have two solutions for this dilemma: Web services discovery, and Web services directories using Universal Description, Discovery, and Integration (UDDI).

Advertising Web Services Through a Discovery Document

A discovery document is an XML-based document containing references to Web services and/or other discovery documents. Through the use of discovery documents, you can easily catalog all of the available Web services on a Web server at a single location.

By publishing a discovery document to a publicly available URL, you can make it possible for clients to easily locate and consume Web services that you have made available. Clients can access the discovery document using tools such as the wsdl.exe command-line utility supplied with the .NET Framework, or with Visual Studio .NET’s Add Web Reference dialog box, in order to create clients for the Web service. We’ll discuss this process in greater detail in “Using a Web Service” on page 429.

A discovery file uses the extension .disco and contains two types of nodes: contractRef nodes and discoveryRef nodes. The contractRef nodes refer to WSDL files that you can use to create clients for a Web service, while discoveryRef nodes refer to additional discovery documents. The discovery document for the two example Web services created in this chapter is shown in the following listing, Chapter11.disco. The ref attribute of the contractRef node contains the URL to the WSDL contract for the Web service, in this case generated by ASP.NET by appending the ?WSDL argument to the URL for the .asmx file that implements the Web service. The docRef attribute contains the URL to the documentation for the Web service, in this case simply the URL to the .asmx file, since ASP.NET automatically generates documentation for Web services implemented in an .asmx file. The xmlns attributes in the discoveryRef and contractRef nodes establish the XML namespaces in which these nodes reside.

Note that for the sake of readability, this listing contains relative URLs. Discovery documents can contain either relative or absolute URLs. When relative URLs are used, they are assumed to be relative to the location of the URL of the discovery document. If you browse to the disco file, it will be displayed just as any other XML file. You can add a .disco file to your project by right-clicking the project in Solution Explorer, then highlighting Add, then selecting Add New Item. In the Add New Item dialog box, select the Static Discovery File icon, and give the file the desired name.

Advertising Web Services Through UDDI

Another option for advertising Web services to potential clients is UDDI. This is a multivendor initiative, spearheaded by Ariba, IBM, and Microsoft, and designed to provide an Internet-based business registry (or registries) in which creators of Web services can register their publicly available Web services. The UDDI specification describes how Web services can be registered in such a way as to allow clients to easily search for the type of Web service desired, or to discover all of the Web services offered by a particular partner or company.

Note

While the UDDI initiative is based on Web standards and proposed standards such as DNS, HTTP, XML, and SOAP, UDDI itself is not a proposed standard. Rather, it’s a multivendor specification, created and maintained by Ariba, IBM, and Microsoft, with input from other companies. It is the stated intent of the involved parties that the UDDI specification will eventually be transitioned to a standards body.

The model for UDDI is for multiple identical versions of the UDDI business registry to be hosted by multiple participants, providing redundancy and load balancing for the registry. Web service providers can register their Web services at any node and the information will be replicated to all registries daily, allowing potential clients to find these services from any registry.

Currently, both Microsoft and IBM provide online UDDI registries, and both can be reached from http://www.uddi.org/, where you can also find a number of helpful white papers on UDDI.

Securing a Web Service

One challenge of implementing a Web service comes when you need to limit who can access it. You might want to limit access to only those clients who have paid a subscription fee, for example. Or, if your Web service stores and uses data specific to individual users, you might want to implement some sort of login process to allow clients to view and modify only data that is specific to them.

One potential issue here is that the SOAP protocol, on which Web services are based, does not provide any specifications for securing Web services. There are newly proposed standards for Web service security, such as WS-Security. (See http://msdn.microsoft.com/library/en-us/dnglobspec/html/wssecurspecindex.asp for more information.) However, until all or most Web Service vendors adopt these standards, their usefulness will be limited to situations in which you can control all parts of the application. This means that it’s up to you to choose an existing means of securing your Web service that provides the level of security you need but that’s also relatively easy to implement, both for you and your clients. The last thing you want is to make your Web service so difficult to use that potential clients go elsewhere. You should keep track, however, of new information as it becomes available, at sites such as http://msdn.microsoft.com/ webservices/default.aspx. Keeping up on the latest information better allows you to determine when new specifications such as WS-security are widely accepted enough for use in your applications.

Exploring Authentication Options

The first choice you must make when securing your Web service is how to authenticate users of your Web service. There are a number of options, each with its own advantages and disadvantages.

While ASP.NET does not define any specific authentication and authorization method for Web services, you can take advantage of the authentication mechanisms that IIS offers. These mechanisms are described in detail in Chapter 6, in the section titled “Using Windows-Based Authentication.”

The advantage of using one of the IIS authentication mechanisms is that the infrastructure for them already exists. You do not need to create your own credential store for user account information. One disadvantage is that each of the authentication methods offered by IIS requires the creation of NT accounts for each client of your application. This can be difficult to administer, depending on how your Web server is set up (whether it is part of an NT domain, whether it uses Microsoft Active Directory, and so on). Another disadvantage is that the Integrated Windows authentication method, while secure, requires the client to belong to the same domain (or a trusted domain) as the server, making it impractical for Internet use. Basic authentication is suitable for Internet use, but it sends username and password as clear text, making it easy for them to be compromised. You can use SSL encryption to prevent this problem, but this can add an unacceptable performance overhead if user credentials must be passed with each Web service request.

Another option for authenticating users is a third-party authentication mechanism such as Microsoft Passport. Unfortunately, the developer tool support for Passport is currently poor, and most available tools focus on allowing the use of Passport to authenticate users from Web sites, rather than Web services. While it is likely that Passport and other third-party authentication mechanisms will add support for simple implementation in Web services eventually, today these mechanisms likely will require too much effort on the part of your clients.

Finally, you can “roll your own” Web services authentication by implementing a login function in one or all of your Web services, with the login method returning a unique key (which should be hashed or encrypted) identifying the logged-in user. This key is then passed as a parameter of each Web service request, indicating that the user is an authenticated user. To protect the username and password, the login Web service method should be requested over an SSL connection. Subsequent requests can be made over a standard HTTP connection, however, so as to avoid the performance overhead of SSL encryption. To reduce the risk of a user being impersonated by someone who has intercepted the login key, these keys should be stored in such a way that they can be easily expired after a predetermined timeout value (in a back-end database, for example).

While this authentication method is more difficult to implement and maintain than some of the others, it’s the simplest for your clients to use. Once you have given a client his account information (username and password), all he needs to do is make the request to the login Web service (over an SSL connection) and store his login key. With each subsequent request, he passes the key as one of the parameters of the Web service method he’s calling. If the timeout value expires between one request and the next, the user must call the login method again to receive a new key. Therefore, the timeout value should be set to provide a balance between security and convenience. In addition to the login method, you could also implement a logout method in your Web service to explicitly expire the login key.

Important

In addition to controlling access to a Web service with authentication, you should also use SSL to encrypt communications if you are passing sensitive data to or from the Web service. This includes usernames and passwords sent when authenticating users, as well as data such as patient information in the healthcare industry, where the requirement to protect personal data is not only common sense, it’s the law!

You can find more information on Web services security, including information on the proposed WS-Security specification, in the Microsoft XML Web Services Developer Center at http://msdn.microsoft.com/webservices/ and at http:// msdn.microsoft.com/ws-security/.