Building and Consuming Web Services using Delphi and .NET

By: Xavier Pacheco

Abstract: The session demonstrates how to create and consume Web Services in Delphi using the Microsoft .NET Framework.

Xavier Pacheco is the president of Xapware Technologies Inc., which he founded in January 1988 and author of Delphi for .NET Developer's Guide. Xavier has over 16 years of professional experience in developing software solutions such as distributed systems, application architectures, and process and design methodologies. Xavier is an internationally recognized developer, author, consultant, and trainer. He has written several books on Delphi, frequently writes articles, and gives presentations at industry conferences. Xavier can kick John Kaster's butt in a game of pool anyday.

xavier@xapware.com

4122 Building and Consuming Web Services using Delphi and .NET

4122 Building and Consuming Web Services using Delphi and .NET

Published in Delphi for .NET Developer's Guide, Sam's Publishing
Chapter 28: Building Web Services
by Xavier Pacheco

There are several reasons why it is important to understand how to develop Web Services. One reason has to do with keeping current on development trends and leading edge technologies. Chapter 1, "Introduction to .NET," gives an overview of Web Services. Another reason is that Web Services enable you to expose your service (or application) in a standard way to any client, implemented on any platform, potentially accessed from anywhere in the world. This chapter delves right into using Delphi for .NET and .NET to create and consume Web Services.

Terms Related to Web Services

The following terms were introduced in Chapter 1. I'm repeating them here for quick reference. These are terms that you will encounter frequently when working with Web Services.

  • XML—Extensible Markup Language. XML is a flexible text-based format originally derived from SGML for the purpose of electronic publishing. XML's richness and self-defining format make it ideal for use in passing messages between Web Service consumers and servers.

  • SOAP—Simple Object Access Protocol. SOAP is the protocol for Web Services based on the XML standard for invoking remote procedure calls over the Internet/intranet. SOAP specifies the format of the request/response and the format of parameters passed in the request/response. SOAP is only specific to the message. It only imposes adherence to the SOAP specifications but is otherwise platform and language agnostic.

  • WSDL—Web Service Description Language. WSDL is an XML-based language used to describe the facilities of a Web Service. This includes all the various methods and their parameters as well as the location of the Web Service. Web Service consumers can understand WSDL and can determine the functionality provided by the Web Service. WSDL is typically used by tools and IDEs to automatically create proxy classes used to access the service.

  • UDDI—Universal Description, Discovery, and Integration. UDDI is a standard for public registries for storing information about publishing Web Services. You can visit UDDI at http://www.uddi.org for information on the UDDI standard. Examples of UDDI registries are http://www.xmethods.net and http://uddi.microsoft.com.

Web Service Construction

In this section, I will demonstrate the process of creating three types of Web Services. One will be a simple Web Service that returns the sum of two numbers. The second one will return a DataSet containing a table from the Northwind database. A third will demonstrate the use of the [WebMethod] attribute.

For the most part, creating the initial files for the Web Service is a step-by-step process when using the wizard from Delphi for .NET.

  1. Select File, New, Other from the main menu to launch the New Items dialog box.

  2. Select the Item Category, Delphi ASP Projects.

  3. Select the ASP.NET Web Service Application icon (see Figure 28.1). Click OK.

Figure 28.1  
ASP.NET Web Service Application icon.

  1. In the Application Name dialog box (see Figure 28.2), enter the name of your Web Service, such as MyFirstWebService, and click OK.

Figure 28.2  
Naming the ASP.NET Web Service application.

That's it! You will now have the files WebService1.asmx and WebService1.pas. Let's examine the WebService1.pas file. Listing 28.1 shows the contents of the file. (Lines irrelevant to this discussion are removed.) Listing 28.1 also contains some additional code, which I've added. This code appears in bold.

Listing 28.1  First Web Service Example

1:   unit WebService1;
2:   
3:   interface
4:   
5:   uses
6:     System.Collections, System.ComponentModel,
7:     System.Data, System.Diagnostics, System.Web,
8:     System.Web.Services;
9:   
10:  type
11:    TWebService1 = class(System.Web.Services.WebService)
12:    strict private
13:      components: IContainer;
14:      procedure InitializeComponent;
15:    strict protected
16:      procedure Dispose(disposing: boolean); override;
17:    public
18:      constructor Create;
19:      // Sample Web Service Method
20:      [WebMethod]
21:      function HelloWorld: string;
22:      [WebMethod]
23:      function Add(A, B: Integer): Integer;
24:    end;
25:  
26:  implementation
27:  
28:  constructor TWebService1.Create;
29:  begin
30:    inherited;
31:    InitializeComponent;
32:  end;
33:  
34:  procedure TWebService1.Dispose(disposing: boolean);
35:  begin
36:    if disposing and (components <> nil) then
37:      components.Dispose;
38:    inherited Dispose(disposing);
39:  end;
40:  
41:  function TWebService1.HelloWorld: string;
42:  begin
43:    Result := 'Hello World';
44:  end;
45:  
46:  function TWebService1.Add(A, B: Integer): Integer;
47:  begin
48:    Result := A + B;
49:  end;
50:  
51:  end.

Find the code on the CD: CodeChapter 28Ex01.


You'll also notice that I've uncommented the HelloWorld() method (lines 41–44). At this point, you have a valid Web Service that can be deployed and consumed externally.

First, you'll see that TWebService1 descends from System.Web.Services.WebService (line 11). It is actually not required that you descend from this class to create a Web Service. However, by doing so, you obtain easy access to common ASP.NET objects such as Application, Context, Session, and so on.

The TWebService1 class looks similar to any other Delphi class. One difference is that the method declarations are preceded by the [WebMethod] attribute. This attribute is the one responsible for specifying which method of the Web Service is exposed externally. Certain properties of this attribute can also enable developers to extend or change the behavior of the method. I'll discuss the [WebMethod] attribute in more detail momentarily.


Note - You'll notice that the [WebMethod] attribute is added by the IDE for the implementation of the HelloWorld() example but commented out with the example. This is incorrect. The [WebMethod] is only needed in the interface section. Adding it to the implementation will result in it being duplicated in the metadata.


At this point, you can test the Web Service by running it.


Tip - An easy way to run the application without debugging it within the Delphi IDE is to Select Run, Run Without Debugging from the main menu. I take it a step further and add the tool button to my toolbar because I frequently use this option. You can do this by right-clicking on the toolbar and selecting the Customize option. From the Commands tab of the Custom dialog box, you can drag the command buttons you want to the toolbar.


Figure 28.3 is HMTL Interface showing the Web Service.

Figure 28.3  
Output from MyFirstWebSerice.

When a Web Service's base URI is requested without any parameters, ASP.NET returns the service description in the form shown in Figure 28.3. You can see that the name of the class that implements the Web Service is shown at the top of the browser. Also, it shows the names of the methods that are available to the user of the Web Service. It then shows some descriptive developer related information about changing the namespace for the Web Service. When you click on the Service Description link, you are taken to the page shown in Figure 28.4.

Figure 28.4  
Service description in XML.

Figure 28.4 shows the Service Description in XML, also known as the WDSL of the Web Service. This is for those of you who would rather examine the service description in XML form. It is also for tools that can automatically generate classes, test user interfaces, and so on. Going back to the previous page (refer to Figure 28.3), you can actually test the methods. In fact, click on the Add link, and you will be taken to the page shown in Figure 28.5.

Figure 28.5  
Testing the Web Service.

You can now enter two integer values in to the TextBox controls and press the Invoke button, which will return the following result:

<?xml version="1.0" encoding="utf-8" ?> 
 <int xmlns="http://tempuri.org/">5</int> 

You can also invoke the Web Service using the HTTP-GET protocol through the following URL:

http://localhost/MyFirstWebService/WebService1.asmx/Add?A=3&B=4

You can also invoke the Web Service using the HTTP-POST protocol using the following .html document:

<html>
<form method="post" action="http://localhost/MyFirstWebService/WebService1.asmx/Add">
<input name="A" value ="2">
<input name="B" value ="4">
<input type="submit" value="Add!">
</form>
</html>

This is all there is to creating a Web Service—a simple one anyway. Later, we'll look at consuming this Web Service into a client application. In the next example, I'll create a Web Service that serves a DataSet to the consumer. First, I need to discuss the [WebService] attribute briefly.

The [WebService] Attribute

When you create a Web Service, three properties of a Web Service get default values. You should change these values prior to publishing your Web Service. These properties are the description, the name, and the namespace. In Figure 28.3, you'll see that the Web Service name is WebService1 and the namespace is the default of http://tempuri.org/. You should especially change the default namespace before releasing your Web Service to production. A convention to use is to form the namespace using a company domain combined with your specific Web Service. For example,

http://www.xapware.com/MyFirstWebService

There need not be any physical file or directory behind the namespace name; it is used only as a logical unique identifier.

You can change these properties through the [WebService] attribute. This attribute is placed above the Web Service class declaration, as shown here:

[WebService(
  Namespace='http://www.xapware.com/MyFirstWebService',
  Name='My First Web Service',
  Description='This is My First Web Service which demonstrates two methods.')]
TWebService1 = class(System.Web.Services.WebService)

By doing this, the output of the Web Service now becomes as shown in Figure 28.6.

Figure 28.6  
MyFirstWebService output after changing the [WebService] attribute.

You'll notice that the Web Service name and description now show what was specified in the [WebService] attribute properties. Additionally, you'll also notice that the descriptions and code example are also removed. This developer information is removed because having a different namespace deems the Web Service as production ready.

Returning Data from a Web Service

To create this Web Service, I followed the same steps as the previous except I called this one MySecondWebService. I also renamed the implementing class to WebService2. In this Web Service, I created the function shown in Listing 28.2.

Listing 28.2  Web Service Demonstrating a DataSet Result

1:   function TWebService2.GetEmployees: DataSet;
2:   const
3:     c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
4:     c_sel   = 'Select * From employees';
5:   var
6:     sqlcn: SqlConnection;
7:     sqlDA: SqlDataAdapter;
8:     ds: DataSet;
9:  begin
10:    try
11:      sqlcn := SqlConnection.Create(c_cnstr);
12:      sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
13:      ds := DataSet.Create;
14:      sqlDA.Fill(ds, 'employees');
15:      sqlcn.Close;
16:      Result := ds;
17:    except
18:      Result := nil;
19:      Ralse;
20:    end;
21:  end;

Find the code on the CD: CodeChapter 28Ex02.


You should recognize this process of retrieving a DataSet from Northwind. Line 17 is the significant line that returns the DataSet class, which will get marshaled as XML, as you will see. When you run the Web Service, you are shown the page in Figure 28.7.

Figure 28.7  
MySecondWebService output.

First, notice the addition of a description underneath the method name. I'll address this in a moment. When you test the Web Service, you'll see the page with XML as shown in Figure 28.8.

Figure 28.8  
XML output of the Northwind employees table.

You should recognize this data from the employee table in the Northwind database. Later, I'll demonstrate how you would consume this DataSet in both an ASP.NET and a Windows Forms application.

The [WebMethod] Attribute Explained

In the previous example, you might have noticed the following line above the GetEmployees() method:

[WebMethod(Description='Gets the employees table')]

It was already mentioned that the [WebMethod] attribute is the one responsible for exposing a method externally. This attribute contains additional properties that you can set such as description, which was demonstrated in the previous example. Table 28.1 lists the various properties of [WebMethod].

Table 28.1  [WebMethod] Properties

Property

Description

BufferResponse

Determines if the response for the request is buffered to memory before sending it back to the client. When True (the default), the response is buffered to memory until complete or until the buffer is full. Then, it is sent to the client. When False, the response is sent in 16Kb chunks. Keep in mind that this size is an implementation detail of the MS CLR v1.1, which could change in the future. You would only set this to False if you know the response will be excessive, which could improve performance, keeping in mind that it could also reduce memory usage and improve (lower) latency.

CacheDuration

This property is useful if you will be invoking a method several times using the same parameter set. It causes the server to cache the response for the specified number of seconds. When a subsequent request is made within the time window, the response is obtained from the cache, thus potentially improving performance.

Description

This property provides a String description of the method.

EnableSession

This property enables/disables session state. When enabled, the Web Service method might make use of the Session object.

MessageName

This property allows you to specify an alias for the method name. This is useful to distinguish between overloaded methods of the same name.

TransactionOption

This property allows the code within a Web Service to work similar to a database transaction. The transaction support is provided through a COM+ level of transactional support.


To demonstrate the use of the [WebMethod] attribute, consider the declaration of the following methods:

[WebMethod]
function Add(A, B: Integer): Integer; overload;
[WebMethod]
function Add(A, B: Double): Double; overload;

When using a class directly, just having the overload directive would be sufficient to use both methods. This is because the compiler would be capable of resolving the methods by their parameters. However, attempting to run a Web Service containing these methods would result in a server error because the SOAP standard does not support overloaded methods; it relies on the method name for method resolution.

To correct this, you would use the MessageName property as shown here:

[WebMethod (
MessageName='AddInt')]
function Add(A, B: Integer): Integer; overload;
[WebMethod(
MessageName='AddDouble')]
function Add(A, B: Double): Double; overload;

Consumers of the Web Service would see and invoke the method according to the names specified as the MessageName property.


Find the code on the CD: CodeChapter 28Ex03.


Consuming Web Services

This section discusses the process of Web Service consumption. Applications that make use of Web Services are said to consume the Web Service and are therefore known as consumers. A Web Service consumer must perform at least three steps in order to use the Web Service. These are discovery (a process of extracting information about a Web Service), generating a proxy class, and using the proxy class to invoke the Web Service methods. The first two steps can be done manually; however, the Delphi IDE provides the integrated tools to make the process much easier. I will walk through the step-by-step process.

The Discovery Process

Before you can use a Web Service, you have to know how to use it. You must discover available methods, properties, parameters, types, and so on. Recall that the WSDL document is how Web Services describe themselves to consumers. Therefore, for each Web Service you intend to use, you'll have to examine this document. It's true that you can examine the WSDL and manually hard-code the Delphi class needed to use the service. However, this is unnecessary because the Delphi IDE will examine the WSDL of the Web Service for you and will perform the necessary steps to allow you to use the Web Service; specifically, it will create a proxy class. The following examples assume that you know the location of the Web Service you intend to use.

Constructing a Proxy Class

In this section, I discuss how to create a client application that consumes the latter two Web Services created earlier in this chapter. The steps are

  1. Create the application and save it to a directory.

  2. Add a Web Reference; this will create the proxy class.

  3. Use the proxy class.

Assuming that step 1 is completed, adding the Web Reference is simple. Simply select the project within the Project Manager in the IDE and invoke the local menu by right-clicking. One of the options is Add Web Reference (see Figure 28.9).

Figure 28.9  
Add Web Reference menu from the Project Manager.

This launches the Add Web Reference dialog box. Through this dialog box, you can enter the URL of known Web Service URLs or you can also select from one of the UDDI directories presented in the dialog box. For now, we'll use the URL from one of the Web Services we created. The URL we need is

http://localhost/MyFirstWebService/WebService1.asmx?WSDL

Specifying the ?WSDL parameter will return the WSDL document that we need to generate a proxy class. When you click the blue arrow button on the Add Web Reference dialog box, you will see the WSDL in the dialog box as shown in Figure 28.10.

Figure 28.10  
Add Web Reference dialog box.

At this point, you can click the Add Reference button to generate the proxy class. Looking at the Project Manager, you will see that some files have been generated and added to your project. These files are

  • WebService1.map—The .map file is an XML file that contains information about the WDSL references for the Web Service.

  • WebService1.wsdl—The .wsdl file is the Service Description file. It contains XML that describes the Web Service interface.

  • WebService1.pas—The .pas file is the source file that contains the proxy class.

The next section discusses exactly what this proxy class is and how to use it in order to consume the Web Service.

Using the Proxy Class

A proxy class is a class that hides the implementation internals of invoking a Web Service. Put another way, the proxy class is a class layer between the HTTP SOAP requests to the Web Server and the code that you will be writing to make those requests. It allows you to work with a Delphi class as you are used to. As you just saw in the previous section, the creation of the proxy class is simple using the IDE. Certainly, you can manually create this class, although it is entirely unnecessary because the IDE performs this task perfectly.

Listing 28.3 shows the class proxy created for the Web Service that we consumed.

Listing 28.3  WebService1 Proxy Class

1:   unit localhost.WebService1;
2:   
3:   interface
4:   
5:   uses System.Diagnostics,
6:     System.Xml.Serialization,
7:     System.Web.Services.Protocols,
8:     System.ComponentModel,
9:     System.Web.Services, System.Web.Services.Description;
10:  
11:  type
12:    [System.Diagnostics.DebuggerStepThroughAttribute]
13:    [System.ComponentModel.DesignerCategoryAttribute('code')]
14:    [System.Web.Services.WebServiceBindingAttribute(
15:      Name='My First Web ServiceSoap',
16:      Namespace='http://www.xapware.com/MyFirstWebService')]
17:    MyFirstWebService = class(System.Web.Services.Protocols.SoapHttpClientProtocol)
18:    public
19:      constructor Create;
20:      [System.Web.Services.Protocols.SoapDocumentMethodAttribute(
21:        'http://www.xapware.com/MyFirstWebService/HelloWorld',
22:         RequestNamespace='http://www.xapware.com/MyFirstWebService',
23:         ResponseNamespace='http://www.xapware.com/MyFirstWebService',
24:         Use=System.Web.Services.Description.SoapBindingUse.Literal,
25:         ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
26:      function HelloWorld: string;
27:      function BeginHelloWorld(callback: System.AsyncCallback;
28:        asyncState: System.Object): System.IAsyncResult;
29:      function EndHelloWorld(asyncResult: System.IAsyncResult): string;
30:      [System.Web.Services.Protocols.SoapDocumentMethodAttribute(
31:        'http://www.xapware.com/MyFirstWebService/Add',
32:         RequestNamespace='http://www.xapware.com/MyFirstWebService',
33:         ResponseNamespace='http://www.xapware.com/MyFirstWebService',
34:         Use=System.Web.Services.Description.SoapBindingUse.Literal,
35:         ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
36:      function Add(A: Integer; B: Integer): Integer;
37:      function BeginAdd(A: Integer; B: Integer; callback: System.AsyncCallback;
38:        asyncState: System.Object): System.IAsyncResult;
39:      function EndAdd(asyncResult: System.IAsyncResult): Integer;
40:    end;
41:  
42:  implementation
43:  
44:  {$AUTOBOX ON}
45:  {$HINTS OFF}
46:  {$WARNINGS OFF}
47:  
48:  constructor MyFirstWebService.Create;
49:  begin
50:    inherited Create;
51:    Self.Url := 'http://localhost/MyFirstWebService/WebService1.asmx';
52:  end;
53:  
54:  function MyFirstWebService.HelloWorld: string;
55:  type
56:    TSystem_ObjectArray = array of System.Object;
57:    TArrayOfSystem_Object = array of System.Object;
58:  var
59:    results: TArrayOfSystem_Object;
60:  begin
61:    results := Self.Invoke('HelloWorld', new(TSystem_ObjectArray, 0));
62:    Result := (string(results[0]));
63:  end;
64:  
65:  function MyFirstWebService.BeginHelloWorld(callback: System.AsyncCallback;
66:    asyncState: System.Object): System.IAsyncResult;
67:  type
68:    TSystem_ObjectArray = array of System.Object;
69:  begin
70:    Result := Self.BeginInvoke('HelloWorld', new(TSystem_ObjectArray, 0),
71:      callback,asyncState);
72:  end;
73:  
74:  function MyFirstWebService.EndHelloWorld(
75:    asyncResult: System.IAsyncResult): string;
76:  type
77:    TArrayOfSystem_Object = array of System.Object;
78:  var
79:    results: TArrayOfSystem_Object;
80:  begin
81:    results := Self.EndInvoke(asyncResult);
82:    Result := (string(results[0]));
83:  end;
84:  
85:  function MyFirstWebService.Add(A: Integer; B: Integer): Integer;
86:  type
87:    TSystem_ObjectArray = array of System.Object;
88:    TArrayOfSystem_Object = array of System.Object;
89:  var
90:    results: TArrayOfSystem_Object;
91:  begin
92:    results := Self.Invoke('Add', TSystem_ObjectArray.Create(A, B));
93:    Result := (Integer(results[0]));
94:  end;
95:  
96:  function MyFirstWebService.BeginAdd(A: Integer; B: Integer;
97:    callback: System.AsyncCallback;
98:    asyncState: System.Object):  System.IAsyncResult;
99:  type
100:   TSystem_ObjectArray = array of System.Object;
101: begin
102:   Result := Self.BeginInvoke('Add', TSystem_ObjectArray.Create(A, B), 
103:       callback, asyncState);
104: end;
105: 
106: function MyFirstWebService.EndAdd(asyncResult: System.IAsyncResult): Integer;
107: type
108:   TArrayOfSystem_Object = array of System.Object;
109: var
110:   results: TArrayOfSystem_Object;
111: begin
112:   results := Self.EndInvoke(asyncResult);
113:   Result := (Integer(results[0]));
114: end;
115: 
116: end.

Find the code on the CD: CodeChapter 28Ex04.


The proxy class serves as a surrogate object for the Web Service and declares methods that can be invoked by the consuming application, which themselves invoke the methods of the Web Service. In fact, it defines two ways that you can invoke the Web Service methods. You can do so synchronously or asynchronously. The synchronous methods are given the same name as they had in the Web Service (or as the MessageName in the [WebMethod] attribute). For instance, lines 26 and 36 show the method declarations for the HelloWorld() and Add() methods from the Web Service. The asynchronous method invocation requires two methods per Web Service method. These are defined using the form BeginFunctionName() EndFunctionName(). You'll see these method declarations on lines 27–29 for the HelloWorld() method and on lines 37–39 for the Add() method.


Note - You might have noticed the following included as a parameter to Self.Invoke() (line 92):

TSystem_ObjectArray.Create(A, B)

This is new Delphi syntax for creating a dynamic array. Given the dynamic array type declared as,

TSystem_ObjectArray = Array of System.Object;

You can create an instance of this type using the forms

TSystem_ObjectArray.Create('hello', 'world');

or

new(TSystem_ObjectArray, 2);

The first form creates and adds two elements to the array. The second simply creates the Array with space for two elements.


Notice that the proxy class descends from the SoapHttpClientProtocol protocol class, which is defined in the System.Web.Services.Protocols namespace. If you wanted to use the HTTP-GET or HTTP-POST protocols, you would descend from the appropriate classes (HttpGetClientProtocol or HttpPostClientProtocol). Additionally, you would have to specify the corresponding attribute, which is the HTTPMethodAttribute.

Using the Web Service is simply too easy. The following code illustrates how you would use this Web Service to add two numbers entered from two TextBox controls:

procedure TWinForm1.Button1_Click(sender: System.Object; e: System.EventArgs);
var
  mfws: MyFirstWebService;
  result: Integer;
begin
  mfws := MyFirstWebService.Create;
  Result := mfws.Add(Convert.ToInt32(TextBox1.Text),
    Convert.ToInt32(TextBox2.Text));
  Label1.Text := Label1.Text + Result.ToString;
end;

Find the code on the CD: CodeChapter 28Ex04.


Consuming a DataSet from a Web Service

In this next example, I consumed the following Web Service through the Add Web Reference dialog:

http://localhost/MySecondWebService/WebService2.asmx

This resulted in the proxy class shown in Listing 28.4.

Listing 28.4  WebService2 Proxy Class

1:   unit localhost.WebService2;
2:   
3:   interface
4:   
5:   uses System.Diagnostics,
6:     System.Xml.Serialization,
7:     System.Web.Services.Protocols,
8:     System.ComponentModel,
9:     System.Web.Services, System.Web.Services.Description, System.Data;
10:  
11:  type
12:    [System.Diagnostics.DebuggerStepThroughAttribute]
13:    [System.ComponentModel.DesignerCategoryAttribute('code')]
14:    [System.Web.Services.WebServiceBindingAttribute(
15:      Name='My Second Web ServiceSoap',
16:      Namespace='http://www.xapware.com/MySecondWebService')]
17:    MySecondWebService = class(System.Web.Services.Protocols.SoapHttpClientProtocol)
18:    public
19:      constructor Create;
20:      [System.Web.Services.Protocols.SoapDocumentMethodAttribute(
21:        'http://www.xapware.com/MySecondWebService/GetEmployees',
22:         RequestNamespace='http://www.xapware.com/MySecondWebService',
23:         ResponseNamespace='http://www.xapware.com/MySecondWebService',
24:         Use=System.Web.Services.Description.SoapBindingUse.Literal,
25:         ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
26:      function GetEmployees: System.Data.DataSet;
27:      function BeginGetEmployees(callback: System.AsyncCallback;
28:        asyncState: System.Object): System.IAsyncResult;
29:      function EndGetEmployees(asyncResult: System.IAsyncResult):
30:        System.Data.DataSet;
31:    end;
32:  
33:  implementation
34:  
35:  {$AUTOBOX ON}
36:  {$HINTS OFF}
37:  {$WARNINGS OFF}
38:  
39:  constructor MySecondWebService.Create;
40:  begin
41:    inherited Create;
42:    Self.Url := 'http://localhost/MySecondWebService/WebService2.asmx';
43:  end;
44:  
45:  function MySecondWebService.GetEmployees: System.Data.DataSet;
46:  type
47:    TSystem_ObjectArray = array of System.Object;
48:    TArrayOfSystem_Object = array of System.Object;
49:  var
50:    results: TArrayOfSystem_Object;
51:  begin
52:    results := Self.Invoke('GetEmployees', new(TSystem_ObjectArray, 0));
53:    Result := (System.Data.DataSet(results[0]));
54:  end;
55:  
56:  function MySecondWebService.BeginGetEmployees(callback: System.AsyncCallback;
57:    asyncState: System.Object): System.IAsyncResult;
58:  type
59:    TSystem_ObjectArray = array of System.Object;
60:  begin
61:    Result := Self.BeginInvoke('GetEmployees', new(TSystem_ObjectArray, 0),
62:      callback, asyncState);
63:  end;
64:  
65:  function MySecondWebService.EndGetEmployees(asyncResult:
66:    System.IAsyncResult): System.Data.DataSet;
67:  type
68:    TArrayOfSystem_Object = array of System.Object;
69:  var
70:    results: TArrayOfSystem_Object;
71:  begin
72:    results := Self.EndInvoke(asyncResult);
73:    Result := (System.Data.DataSet(results[0]));
74:  end;
75:  
76:  end.

Find the code on the CD: CodeChapter 28Ex05.


In Listing 28.4, you'll see the declaration of the methods GetEmployees(), BeginGetEmployees(), and EndGetEmployees()—each of which returns a DataSet instance. Using this Web Service is no different from the previous in terms of its simplicity. The following code invokes the synchronous method, GetEmployees() and assigns the results to its own DataSet instance. This instance, which is bound to a DataGrid, results in the output shown in Figure 28.11.

procedure TWinForm.Button1_Click(sender: System.Object; e: System.EventArgs);
var
  ds: DataSet;
  msws: MySecondWebService;
begin
  msws := MySecondWebService.Create;
  ds := msws.GetEmployees;
  DataGrid1.DataSource := ds.Tables['Employees'];
end;

Find the code on the CD: CodeChapter 28Ex05.


Figure 28.11  
Output of a DataSet from a Web Service.


Note - When returning a DataSet from a method, it makes the method .NET specific because another type of client would not understand how to use the resultset.


Invoking an Asynchronous Web Service Method

The proxy class creates a pair of methods that you use to invoke a Web Service method asynchronously. Listing 28.5 illustrates how to do this using the same example from Listing 28.4.

Listing 28.5  WebService2 Method Invoked Asynchronously

1:   procedure TWinForm.Button2_Click(sender: System.Object;
2:     e: System.EventArgs);
3:   var
4:     ds: DataSet;
5:     msws: MySecondWebService;
6:     ar: IAsyncResult;
7:   begin
8:     msws := MySecondWebService.Create;
9:     ar := msws.BeginGetEmployees(nil, nil);
10:  
11:    // Do other local processing
12:    ar.AsyncWaitHandle.WaitOne;
13:  
14:    ds := msws.EndGetEmployees(ar);
15:    DataGrid1.DataSource := ds.Tables['Employees'];
16:  end;

Find the code on the CD: CodeChapter 28Ex05.


Most asynchronous usage will likely be more complex than that shown here. The purpose of this example is to show you how these methods are invoked. Line 9 calls the BeginGetEmployees() method, which returns an IAsyncResult instance. IAsyncResult is an interface that represents the status of an asynchronous operation. It has a few properties—one of which is the AsyncWaitHandle. You use this handle to wait for an asynchronous operation to complete. Optionally, you could provide your own AsyncCallBack method to the BeginGetEmployees() method.

On line 12, the AsyncWaitHandle.WaitOne() method is invoked. This method blocks the current thread until a signal is received by the AsyncWaitHandle. When a signal is received, the operation is complete and the EndGetEmployees() method is invoked. This method returns the DataSet instance that is then bound to the DataGrid.

Securing Web Services

There are likely several ways to secure a Web Service. One involves general security of IIS. Another involves user access to the Web Service through authentication. I'll discuss the latter here. General security will be discussed in Chapter 31, "Securing ASP.NET Applications."

The typical way one might secure a resource is through a username/password combination. In database applications, for instance, you might store an encrypted form of the user's password in the database. When the user attempts to log in to the application, the application requests the friendly name of the password, encrypts it, and compares it with what resides in the database. This approach is more secure using a one-way hashing algorithm such as MD5-hashing. Otherwise, perpetrators would be able to derive the password from its hashed form.

This same approach can be used in securing a Web Service. As long as you have stored the encrypted form of the user's credentials, you will be able to authenticate the user.

One way to accomplish this would be to pass the username and password to the methods; however, as you can probably imagine, this would become quite cumbersome having to define and expect the consumers of your Web Services to pass a username, password pair with every method invocation.

A better way is to pass the authentication information in the SOAP header. A SOAP header is XML information that is attached to the SOAP request sent to the Web Service. It is a convenient way to pass along additional information with the request that is not part of the parameter list.

For this to work, your Web Service must declare a class that descends from the SoapHeader class. SoapHeader is defined in the System.Web.Services.Protocols namespace. This class contains the properties that the Web Service needs to authenticate the user. The Web Service class also contains a public instance of the SoapHeader descendant that will be accessible to both the Web Service and the consumer. Listing 28.6 shows a Web Service that makes use of a SoapHeader descendant class.

Listing 28.6  SoapHeader Descendant Class for User Authentication

1:   unit WebService1;
2:   
3:   interface
4:   
5:   uses
6:     System.Collections, System.ComponentModel,
7:     System.Data, System.Diagnostics, System.Web,
8:     System.Web.Services, System.Web.Services.Protocols,
9:     System.Security.Cryptography, System.Text;
10:  
11:  type
12:  
13:    TAuth = class(SoapHeader)
14:      UserName: String;
15:      Password: String;
16:    end;
17:  
18:    TWebService1 = class(System.Web.Services.WebService)
19:    strict protected
20:      procedure Dispose(disposing: boolean); override;
21:    private
22:      function Authenticate(aUserName, aPassword: String): Boolean;
23:    public
24:      Auth: TAuth;
25:      constructor Create;
26:      [WebMethod(), SoapHeader('Auth')]
27:      function HelloWorld: string;
28:    end;
29:  
30:  implementation
31:  
32:  constructor TWebService1.Create;
33:  begin
34:    inherited;
35:    InitializeComponent;
36:  end;
37:  
38:  procedure TWebService1.Dispose(disposing: boolean);
39:  begin
40:    if disposing and (components <> nil) then
41:      components.Dispose;
42:    inherited Dispose(disposing);
43:  end;
44:  
45:  function TWebService1.HelloWorld: string;
46:  begin
47:    if Auth = nil then
48:      raise Exception.Create('Invalid Login');
49:  
50:    if Authenticate(Auth.UserName, Auth.Password)  then
51:      Result := ' You are logged in.'
52:    else
53:      Result := 'Incorrect username/password combo';
54:  end;
55:  
56:  function TWebService1.Authenticate(aUserName, aPassword: String): Boolean;
57:  var
58:    PasswordInDB: array of byte;
59:    encoder: UTF8Encoding;
60:    md5Hasher: MD5CryptoServiceProvider;
61:    HashedBytes: array of byte;
62:    dbPWStr: String;
63:    aPWStr: String;
64:    i: integer;
65:  begin
66:    try
67:      encoder := UTF8Encoding.Create;
68:      md5Hasher := MD5CryptoServiceProvider.Create;
69:      // Pretend you've gotten the hashed password from the Database
70:      PasswordInDB := md5Hasher.ComputeHash(encoder.GetBytes('ZacharyCamaro'));
71:  
72:      aPWStr := aUserName + APassword;
73:      // Hash the password passed in.
74:      HashedBytes := md5Hasher.ComputeHash(encoder.GetBytes(aPWStr));
75:      aPWStr := '';
76:  
77:      // Convert to regular strings
78:      for i := Low(PasswordInDB) to High(PasswordInDB) do7979: dbPWStr := dbPWStr + Convert.ToString(PasswordInDB[i], 16);
80:  
81:      for i := Low(HashedBytes) to High(HashedBytes) do
82:        aPWStr :=   aPWStr + Convert.ToString(HashedBytes[i], 16);
83:  
84:      // Compare
85:      Result := System.String.Compare(dbPWStr, aPWStr) = 0;
86:  
87:    except
88:      Result := False;
89:    end;
90:  end;
91:  
92:  end.

Find the code on the CD: CodeChapter 28Ex06.


Lines 13–16 show the declaration of the SoapHeader descendant class, TAuth. This class contains two fields, UserName and Password. If you wanted to pass additional information to this Web Service, this is where you would declare those fields.

Notice also that the Web Service declares an instance of the TAuth class (line 24). Another modification to the Web Service is to add the SoapHeader attribute to the Web Service's methods, which enables the Web Service to receive the header, create the TAuth instance, and set the corresponding fields. This attribute takes the name of the SoapHeader instance—in this case, 'Auth'—as a parameter.

You will also see the declaration of an Authenticate() method, which I will discuss in a minute.

In examining the HelloWorld() method (lines 45–54), you see that if Auth is not provided, the Web Service raises an exception. The consumer must provide an instance of this class, or there is the possibility of unauthorized access. Line 50 invokes the Authenticate() method, passing the Auth.UserName and Auth.Password members as parameters. Later, you'll see how these get initialized by the consuming application. Authenticate() will return True or False depending on whether valid user credentials were provided.

The Authenticate() method is where the meat is. This method uses the class for computing the MD5 hash on the user's credentials, which it then compares to fictional, database derived credentials to see if there is a match. If there is, the user is authorized; otherwise, access to the Web Service is rejected.

On the client side, the method that uses this Web Service is shown in the following code:

procedure TWinForm.Button1_Click(sender: System.Object; e: System.EventArgs);
var
  ws: TWebService1;
begin
  ws := TWebService1.Create;
  ws.TAuthValue := TAuth.Create;
  ws.TAuthValue.UserName := tbxUserName.Text;
  ws.TAuthValue.Password := tbxPassword.Text;
  TextBox1.Text := ws.HelloWorld;
end;

Find the code on the CD: CodeChapter 28Ex07.


In this code, you'll see that the client is responsible for creating the instance of the TAuth instance. The Delphi IDE generated this member as TAuthValue within the proxy class. In this example, the UserName and Password strings are retrieved from two TextBox controls. TextBox1 will be populated with the result of the function, which will indicate authorization or rejection.

There is a drawback to the scheme provided here. That is, the information you are passing to the Web Service is in plain text format. Obviously, somebody curious would be able to examine the XML packet if he really wanted to. You could, however, use a similar encryption scheme prior to sending information to the Web Service.


  Latest Comments  View All Add New RSS ATOM

Move mouse over comment to see the full text

Server Response from: SC3