Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]

.pdf
Скачиваний:
106
Добавлен:
16.08.2013
Размер:
29.8 Mб
Скачать

1098 C H A P T E R 3 2 C R E AT I N G W E B S E R V I C E S

Table 32-3 lists the supported web service data types.

Table 32-3. Web Service Data Types for Parameters and Return Values

Data Type

Description

The Basics

Simple C# data types such as integers (short, int, long), unsigned

 

integers (ushort, uint, ulong), nonintegral numeric types (float,

 

double, decimal), and a few other miscellaneous types (bool, string,

 

char, byte, and DateTime).

Arrays

You can use arrays of any supported type. You can also use an

 

ArrayList (which is simply converted into an array), but you can’t use

 

more specialized collections such as the Hashtable. You can also use

 

binary data through byte arrays. Binary data is automatically Base64

 

encoded so that it can be inserted into an XML web service message.

Custom Objects

You can pass any object you create based on a custom class or

 

structure. The only limitation is that only public data members are

 

transmitted, and all public members and properties must use one of

 

the other supported data types. If you use a class that includes custom

 

methods, these methods will not be transmitted to the client, and they

 

will not be accessible to the client.

Enumerations

Enumerations types (defined in C# with the enum keyword) are

 

supported. However, the web service uses the string name of the

 

enumeration value (not the underlying integer).

XmlNode

Objects based on System.Xml.XmlNode are representations of a

 

portion of an XML document. You can use this to send arbitrary XML.

DataSet and DataTable

You can use the DataSet and DataTable to return information from a

 

relational database. Other ADO.NET data objects, such as Data-

 

Columns and DataRows, aren’t supported. When you use a DataSet or

 

DataTable, it’s automatically converted to XML in a similar way as if

 

you had used the GetXml() or WriteXml() method.

 

 

Note The supported web service data types are based on the types defined by the XML Schema standard. These map fairly well to the basic set of C# data types.

The EmployeesServices class follows these rules. The only data types it uses for parameters and return values are int and DataSet, both of which are supported. Of course, some web service programmers prefer to steer clear of the DataSet (see the sidebar “The DataSet and XML Web Services”), but it’s still a reasonable, widely used approach.

One other requirement is that your web services should be stateless. In fact, the web service architecture works in the same way as the web-page architecture—a new web service object is created at the beginning of the request, and the web service object is destroyed as soon as the request has been processed and the response has been returned. The EmployeesServices class fits well with this model, because it doesn’t retain any state in class member variables. The only exception is the connectionString variable, which is initialized with the required value every time the class is created.

C H A P T E R 3 2 C R E AT I N G W E B S E R V I C E S

1099

THE DATASET AND XML WEB SERVICES

You’ll notice that the DataSet is one of the few specialized .NET classes that is supported by web services. That’s because the DataSet has the ability to automatically serialize itself to XML. However, this support comes with a significant caveat—even though non-.NET clients can use a web service that returns a DataSet, they might not be able to do anything useful with the DataSet XML! That’s because other languages won’t be able to automatically convert the DataSet into a manageable objects. Instead, they will be forced to use their own XML programming APIs. Although these work in theory, they can be tedious in practice, especially with complex, proprietary XML. For that reason, developers usually avoid the DataSet when creating web services that need to support clients on a wide range of platforms.

It’s worth noting that Microsoft could have used the DataSet approach with many other .NET classes in order to make it possible for them to be serialized into XML. However, Microsoft wisely restrained itself from adding these features, realizing this would make it far too easy for programmers to create applications that used web service standards but weren’t practical in cross-platform scenarios. (Not so long ago, Microsoft might have pursued exactly this “embrace and extend” philosophy, but fortunately it has recognized the need to foster integration and broad compatibility between applications.)

So that still leaves the question of why Microsoft decided to support the DataSet in its web services toolkit. The reason is because the DataSet enables one of the most common uses of web services—returning a snapshot of information from a relational database. The benefit of adding this feature seemed worth the cost of potential interoperability headaches for developers who don’t consider their web service architecture carefully.

Support for Generics

Web services support generics. However, this support might not be exactly what you expect. It’s completely acceptable to create a web service method that accepts or returns a generic

type. For example, if you want to return a collection of EmployeeDetails objects, you could use the generic List class, as shown here:

public List<EmployeeDetails> GetEmployees() { ... }

In this case, .NET treats your collection of EmployeeDetails objects in the same way as an array of EmployeeDetails objects:

public EmployeeDetails[] GetEmployees() { ... }

Of course, for this to work, there can’t be anything that breaks the serialization rules in the EmployeeDetails class or the List class. For example, if these classes have a nonserializable property, the entire object can’t be serialized.

The reason .NET supports generics in this example is because it’s quite easy for .NET to determine the real class types at compile time. That allows .NET to determine the structure of the XML messages this method will use and add the information to the WSDL document (as you’ll see in the next chapter).

However, .NET doesn’t support generic methods. For example, this method isn’t allowed:

public List<T> GetEmployees<T>() { ... }

Here the GetEmployees() method is itself generic. It allows the caller to choose a type that will be used by the method. Because this method could in theory be used with absolutely any type of document, there’s no way to document it properly and determine the appropriate XML message format in advance.

1100 C H A P T E R 3 2 C R E AT I N G W E B S E R V I C E S

Exposing a Web Service

Now that you’ve verified that the EmployeesService class is ready for the Web, it’s time to convert it to a web service. The crucial first step is to add the System.Web.Services.WebMethod attribute to each method you want to expose as part of your web service. This web service instructs ASP.NET to make this method available for inspection and remote invocation.

Here’s the revised class with two web methods:

public class EmployeesService

{

[WebMethod()]

public int GetEmployeesCount() { ... }

[WebMethod()]

public DataSet GetEmployees() { ... }

}

These two simple changes complete the transformation from your class into a web service. However, the client still has no entry point into your web service—in other words, there’s no way for another application to trigger your web methods. To allow this, you need to create an .asmx file that exposes the web service.

Note In this example, the web service contains the data access code. However, if you plan to use the same code in a web application, it’s worth adding an extra layer using database components. To implement this design, you would first create a separate database component (as described in Part 2) and then use that database component directly in your web pages and your web service.

ASP.NET implements web services as files with the .asmx extension. As with a web page, you can place the code for a web service directly in the .asmx or in a class in a code-behind file that the

.asmx file references (which is the Visual Studio approach).

For example, you could create a file named EmployeesService.asmx and link it to your EmployeesService class. Every .asmx file begins with a WebService directive that declares the serverside language used in the file and the class. It can optionally declare other information, such as the code-behind file and whether you want to generate debug symbols during the compilation. In this respect it is similar to the Page directive for .aspx files.

Here’s an example .asmx file with the EmployeesService:

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

In this case, you have two choices. You can insert the class code immediately after the WebService attribute, or you can compile it into one of the assemblies in the Bin directory. If you’ve added the EmployeesService class to a Visual Studio project, it will automatically be compiled as part of the web application DLL, so you don’t need to include anything else in the .asmx file.

At this point, you’re finished. Your web service is complete, available, and ready to be used in other applications.

Tip There’s no limit to how many web services you add to a single web application, and you can freely mingle web services and web pages.

C H A P T E R 3 2 C R E AT I N G W E B S E R V I C E S

1101

Web Services in Visual Studio

If you’re using Visual Studio, you probably won’t go through the process of creating a class, converting it a web service, and then adding an .asmx file. Instead, you’ll create the .asmx file and the code-behind in one step, by selecting Website Add New Item from the menu. You can choose to put the web service code directly in the .asmx file or in a separate code-behind file, just as you can with a web page.

So, you haven’t seen two other web service details. First, the web service class inherits from System.Web.Services.WebService, and second, a WebService attribute is applied to the class declaration. Neither of these details is required, but you’ll consider their role in the following sections.

Deriving from the WebService Class

When you create a web service in Visual Studio, your web service class automatically derives from the base WebService class, as shown here:

public class EmployeesService : System.Web.Services.WebService { ... }

Inheriting from the WebService class is a convenience that allows you to access the built-in ASP.NET objects (such as Application, Session, and User) just as easily as you can in a web form. These objects are provided as properties of the WebService class, which your web service acquires through inheritance. If you don’t need to use any of these objects (or if you’re willing to go through the static HttpContext.Current property to access them), you don’t need to inherit.

Here’s how you would access Application state in a web service if you derive from the base WebService class:

// Store a number in session state. Session["Counter"] = 10;

Here’s the equivalent code you would need to use if your web service class doesn’t derive from WebService:

// Store a number in session state. HttpContext.Current.Session["Counter"] = 10;

This technique won’t actually work as intended (in other words, the client won’t keep the same session across multiple web method calls) unless you take some extra steps, as described later in the “EnableSession” section.

Table 32-4 lists the properties you receive by inheriting from WebService.

Table 32-4. WebService Properties

Property

Description

Application

An instance of the HttpApplicationState class that provides access to the global

 

application state of the web application

Context

An instance of the HttpContext class for the current request

Server

An instance of the HttpServerUtility class

Session

An instance of the HttpSessionState class that provides access to the current

 

session state

User

An IPrincipal object that allow you to examine user credentials and roles, if the

 

user has been authenticated

 

 

1102 C H A P T E R 3 2 C R E AT I N G W E B S E R V I C E S

Since the .NET Framework supports only single inheritance, inheriting from WebService means your web service class cannot inherit from other classes. This is really the only reason not to inherit from WebService.

Note An interesting point with inheriting from WebService is that WebService is derived from the System.MarshalByRefObject class. This class is the base class used for .NET remoting. As a result, when you create a class that derives from WebService, you gain the ability to use your class in several ways. You can use it as any other local class (and access it directly in your web pages), you can expose it as part of a web service, or you can expose it as a distributed object in a .NET remoting host. To learn more about .NET remoting, refer to

Advanced .NET Remoting, Second Edition (Apress, 2005).

Documenting a Web Service

Web services are self-describing, which means ASP.NET automatically provides all the information the client needs about what methods are available and what parameters they require. This is provided by the XML-based standard called WSDL, which you’ll explore in the next chapter. However, although a WSDL document describes the mechanics of the web service, it doesn’t describe its purpose or the meaning of the information supplied to and returned from each method. Most web services will provide this information in separate developer documents. However, you can (and should) include a bare minimum of information with your web service by using the WebMethod and WebService attributes.

You can add descriptions to each method through the Description property of the WebMethod attribute and to the entire web service as a whole using the Description property of the WebService attribute. You can also apply a descriptive name to the web service using the Name property of the WebService attribute. Here’s an example of how you might insert this information in the EmployeesService:

[WebService(Name="Employees Service", Description="Retrieve the Northwind Employees")]

public class EmployeesService : System.Web.Services.WebService

{

[WebMethod(Description="Returns the total number of employees.")] public int GetEmployeesCount()

{ ... }

[WebMethod(

Description="Returns the full list of employees.")] public DataSet GetEmployees()

{ ... }

}

These custom descriptions are added to the WSDL document that describes your service. It’s also shown in the automatically generated test page that you’ll use in the next section.

You should supply one other detail for your web service—a unique XML namespace. This allows your web service (and the XML messages it generates) to be uniquely identified. XML namespaces were first introduced in Chapter 12. By default, ASP.NET web services use the default XML namespace http://tempuri.org/, which is suitable only for testing. If you don’t set a custom namespace, you’ll see a warning message in the test page advising you to use something more distinctive. Note that the XML namespace has no relationship to the concept of .NET namespaces. It doesn’t affect how your code works or how the client uses your web service. Instead, the XML namespace simply identifies your web service. XML namespaces usually look like URLs. However, they don’t need to correspond to a valid Internet location.

C H A P T E R 3 2 C R E AT I N G W E B S E R V I C E S

1103

Ideally, the namespace you use will refer to a URL address that you control. Often, this will incorporate your company’s Internet domain name as part of the namespace. For example, if your company uses the website http://www.mycompany.com, you might give the Employees web service a namespace such as http://www.mycompany.com/EmployeesService.

The namespace is specified through the WebService attribute, as shown here:

[WebService (Name="Employees Service", Description="Retrieve the Northwind Employees", Namespace="http://www.apress.com/ProASP.NET/")]

public class EmployeesService : System.Web.Services.WebService { ... }

Testing a Web Service

Now that you’ve seen how to create a simple web service, you’re ready to test it. Fortunately, you don’t need to write a client application to test it because .NET includes a test web page that ASP.NET uses automatically when you request the URL of an .asmx file in a browser. This page uses reflection to read and show information about the web services, such as the names of the methods it provides.

To try the test page, request the EmployeesService.asmx file in your browser. (In Visual Studio, you simply need to set this as the start page for your application and then run it.) Figure 32-3 shows the test page you’ll see.

Figure 32-3. The web service test page

Note that the page displays the two web methods with their descriptions, and the page’s title is the name of the web service. If you click one of the methods, you’ll see a page that allows you to test the method (and supply the data for any method parameters). Figure 32-4 shows the page that allows you to test the GetEmployeesCount() method.

1104 C H A P T E R 3 2 C R E AT I N G W E B S E R V I C E S

Figure 32-4. Testing a web method

When you click the Invoke button, a new web page appears with an XML document that contains the requested data. Looking at Figure 32-5, you will see nine employee records. If you look at the URL, you’ll see that it incorporates the .asmx file, followed by the web service method name.

Figure 32-5. The results for GetEmployeesCount()

You can repeat this process to invoke GetEmployees(), in which case you’ll see the much more detailed XML that represents the entire DataSet contents (as shown in Figure 32-6).

As you can see, thanks to this helper page, testing a basic web service is quite straightforward and doesn’t require you to build a client.

C H A P T E R 3 2 C R E AT I N G W E B S E R V I C E S

1105

Figure 32-6. The results for GetEmployees()

The test pages aren’t part of the web services standards; they’re just a frill provided by ASP.NET. In fact, the test page is rendered by ASP.NET on the fly using the web page c:\[WinDir]\Microsoft. NET\Framework\[Version]\Config\DefaultWsdlHelpGenerator.aspx. In some cases, you may want to modify the appearance or behavior of this page. If so, you simply need to copy the DefaultWsdlHelpGenerator.aspx file to your web application directory, modify it, and then change the web.config file for the application to point to the new rendering page by adding the <wsdlHelpGenerator> element, as shown here:

<configuration>

<system.web>

<webServices>

<wsdlHelpGenerator href="MyWsdlHelpGenerator.aspx"/> </webServices>

<!-- Other settings omitted. --> </system.web>

</configuration>

This technique is most commonly used to change the look of the test page. For example, you might use this technique to substitute a version of the page that has a company logo or copyright notice.

1106 C H A P T E R 3 2 C R E AT I N G W E B S E R V I C E S

Consuming a Web Service

Before a client can use a web service, the client must be able to create, send, receive, and understand XML-based messages. This process is easy in principle but fairly tedious in practice. If you had to implement it yourself, you would need to write the same low-level infrastructure code again and again.

Fortunately, .NET provides a solution with a dedicated component called a proxy class, which performs the heavy lifting for your application. The proxy class wraps the calls to the web service’s methods. It takes care of generating the correct SOAP message format and managing the transmission of the messages over the network (using HTTP). When it receives the response message, it also converts the results back to the corresponding .NET data types.

Note To access a web service from another computer, the web service needs to be available. This means you can’t rely on just the built-in Visual Studio web server (which dynamically chooses a new port each time you run it). Instead, you need to create a virtual directory for your web service (as described in Chapter 18). Once you’ve taken this step, you should try requesting the web service in your browser using the virtual directory name to make sure it’s accessible. You can then add a reference to the web service by following the steps in this section.

Figure 32-7 represents this process graphically. In this example, a browser is running an ASP.NET web page, which is using a web service from another server behind the scenes. The ASP.NET web page uses the proxy class to contact this external web service.

Figure 32-7. The web service proxy class

C H A P T E R 3 2 C R E AT I N G W E B S E R V I C E S

1107

Note Thanks to the proxy class, you can call a method in a web service as easily as you call a method in a local component. Of course, this behavior isn’t always a benefit. Web services have different characteristics than local components. For example, it takes a nontrivial amount of time to call a web method, because every call needs to be converted to XML and sent over the network. The danger is that the more this reality is hidden from developers, the less likely they are to take it into account and design their applications accordingly.

You can create a proxy class in .NET in two ways:

You can use the wsdl.exe command-line tool.

You can use the Visual Studio web reference feature.

Both of these approaches produce essentially the same result, because they use the same classes in the .NET Framework to perform the actual work. In fact, you can even harness these classes (which are found in the System.Web.Services namespaces) to generate your own proxy classes programmatically, although this approach isn’t terribly practical.

In the following sections, you’ll learn how to use wsdl.exe and Visual Studio to create proxy classes. You’ll learn how to consume a web service in three types of clients—an ASP.NET web page, a Windows application, and a classic ASP page.

Tip One difference between the wsdl.exe approach and the web reference feature is that if you use the web reference feature in a web application, you won’t be able to actually see the proxy code (because it’s generated later in the compilation process). This means if you want to tweak the proxy class code or just peek under the hood, and you’re creating a web client, you need to use wsdl.exe. This limitation doesn’t apply to other types of clients. They don’t use the ASP.NET compilation model, so the proxy class code is added directly to the project.

Generating the Proxy Class with wsdl.exe

The wsdl.exe tool takes a web service and generates the source code of the proxy class in either VB .NET or C#. The name WSDL stems from the web service standard (Web Services Description Language) that’s used to describe the functionality provided by a web service. You’ll learn more about WSDL in the next chapter.

You can find the wsdl.exe file in the .NET Framework directory, which is typically in a path similar to c:\Program Files\Microsoft Visual Studio 2005\SDK\v2.0\Bin (depending on the version of Visual Studio you have installed). This file is a command-line utility, so it’s easiest to use by opening a command prompt window.

In ASP.NET, you can request a WSDL document by specifying the URL of the web service plus the ?WSDL parameter. (Alternatively, you can use a different URL or even a file containing the WSDL content.) The minimum syntax to generate the class is the following:

wsdl http://localhost/WebServices1/EmployeesService.asmx

By default the generated class is in the C# language, but you can change it by adding the /language parameter, as follows:

wsdl /language:VB http://localhost/WebServices1/EmployeesService.asmx