Non-visual Web Methods

Non-visual Web Methods provide a way to call a procedure on the server side from browser-side code without the need to create a new WebMethod object in the IDE. Non-visual Web Methods are used when complex data is needed to be passed as part of the parameters of a WebMethod.

For example, suppose we are writing some kind of webmail application. Two features we need are checking for new messages and deleting messages in the trash. Obviously these will require communication between the browser and server sides. Also, it is important that only authorized users are able to perform these tasks.

We add the following server module to our project:


FX Code

Unit Module1;
 
Interface
 
Uses
    SystemSerialiser,
    SystemWebMethod;
 
Type
 
TLoginCredentials = Class(TSerializable)
    Username : String;
    Password : String;
End;
 
SecureWebMethod = Class(WebMethod)
    LoginCredentials : TLoginCredentials; ['WSPublished=True','WSFieldKind=In','WSHeader=True'];
    Procedure ExecuteActions; Override;
End; ['WSAbstract=True'];
 
CheckForNewMessages = Class(SecureWebMethod)
    FoundNewMessages : Boolean; ['WSPublished=True','WSFieldKind=Out'];
    Procedure Execute; Override;
End; ['WSPublished=True'];
 
DeleteMessagesInTrash = Class(SecureWebMethod)
    Procedure Execute; Override;
End; ['WSPublished=True'];
 
 
Implementation
 
Procedure SecureWebMethod.ExecuteActions;
Begin
    If Not LoginCredentials.Username.Equals('username') Or
       Not LoginCredentials.Password.Equals('password') Then
          Raise Exception.Create('invalid username/password');
End;
 
Procedure CheckForNewMessages.Execute;
Begin
    FoundNewMessages := False;
    { ... implementation ... }
End;
 
Procedure DeleteMessagesInTrash.Execute;
Begin
    { ... implementation ... }
End;
 
End.



There are a few points worth highlighting regarding the code above:

(1) SecureWebMethod has a parameter of type TLoginCredentials, which is a class. Any class that descends from TSerializable can be used as a parameter for non-visual Web Methods.

(2) SecureWebMethod is an abstract Web Method (as indicated by the compiler metadata tag 'WSAbstract=True'). It is not called directly; rather, it is used as an ancestor class for the Web Methods CheckForNewMessages and DeleteMessagesInTrash. The purpose of this hierarchy is to avoid the need to re-implement the user authentication code in each Web Method that requires it.

(3) The user authentication is performed in SecureWebMethod.ExecuteActions. The ExecuteActions method of a Web Method is called by the system before the Execute method, allowing the developer to block execution of the Web Method by raising an exception. (Obviously the authentication system shown here is very primitive and not suitable for a real-world application ;-))

(4) The compiler metadata tag 'WSPublished=True' seen in the above code indicates that the Web Method or Web Method parameter that precedes it is to be included in the WSDL file generated by the Morfik compiler for the project. Note that abstract Web Methods cannot be published.

(5) The 'WSFieldKind=' compiler metadata tag indicates the direction in which the parameter should be passed when a call is made to the Web Method. There are 3 possible values:

(a) 'WSFieldKind=In' -- the parameter is passed from the caller to the callee only

(b) 'WSFieldKind=Out' -- the parameter is passed from the callee to the caller only

(c) 'WSFieldKind=In/Out' -- the parameter is passed in both directions

(6) The 'WSHeader=True' compiler metadata tag after the LoginCredentials parameter indicates that the parameter is to be passed in the header rather than the body of the XML. This is often done for parameters that are common to many Web Methods in the project.

After adding these Web Method declarations, we must compile our project before the Web Methods are available for use in the browser side. We can call the Web Methods from the browser-side code of a form like this:


FX Code

Unit Index;
 
Interface
 
Uses
    SystemRPC,
    SystemInternalServices;
 
Type
Index=Class(Form)
    Button1   : Button;
    Button2   : Button;
    TextEdit1 : TextEdit;
    TextEdit2 : TextEdit;
    Procedure Button1Click(Event: TDOMEvent); Message;
  Private
    { Private declarations }
  Public
    { Public declarations }
    Procedure OnWebMethodReturn(SoapClient : TSoapClient);
End;
 
Implementation
 
Procedure Index.Button1Click(Event: TDOMEvent);
Var
    CheckForNewMessages : TCheckForNewMessages;
Begin
    CheckForNewMessages := TCheckForNewMessages.Create;
    CheckForNewMessages.LoginCredentials.Username := TextEdit1.Text;
    CheckForNewMessages.LoginCredentials.Password := TextEdit2.Text;
    CheckForNewMessages.OnWebMethodReturn := @Self.OnWebMethodReturn;
    CheckForNewMessages.Execute;
End;
 
Procedure Index.OnWebMethodReturn(SoapClient : TSoapClient);
Begin
    If TCheckForNewMessages(SoapClient).FaultCode <> '' Then
        ShowMessage('The following error was encountered:\n' + TCheckForNewMessages(SoapClient).FaultDetails)
    Else If TCheckForNewMessages(SoapClient).FoundNewMessages Then
        Showmessage('You have new mail.')
    Else
        Showmessage('You have no new mail.');
End;
 
End.

Additional points about the above code:

(7) Non-visual Web Method calls are asynchronous; you must supply an OnWebMethod return handler to perform any necessary actions in the browser side when the Web Method returns.

(8) Upon the return of the Web Method in the browser side, TSoapClient.FaultCode and TSoapClient.FaultDetails contain details of any exception that was raised in the server-side code of the Web Method.

Sending/receiving a list of objects

Sometimes you might need to send or receive an array of objects. To do so declare web method parameter using List Of construct. Consider a simple webmethod returning the list of customers with name and e-mail for each customer. Here is how it can be done:

FX Code

Type
TCustomer = Class(TSerializable)
    Name  : String;
    EMail : String;
End;
 
GetCustomers = Class(WebMethod)
    Customers : List Of TCustomer; ['WSPublished=True','WSFieldKind=Out'];
    Procedure Execute; Override;
End; ['WSPublished=True'];
 
Implementation
 
Procedure GetCustomers.Execute;
Var
    Customer : TCustomer;
Begin
    Customer := TCustomer.Create;
    Customer.Name := 'Alice';
    Customer.EMail := 'alice@wonderland.com';
    Customers.Add(Customer);
 
    Customer := TCustomer.Create;
    Customer.Name := 'Bob';
    Customer.EMail := 'bob@somewhere.com';
    Customers.Add(Customer);
End;


On the browser side you can use this web service like this:

FX Code

Uses
    SystemRPC,
    SystemInternalServices;
...
Procedure Index.HandleGetCustomers(SoapClient : TSoapClient);
Var
    GetCustomers : TGetCustomers;
    Customer     : TCustomer;
    I            : Integer;
Begin
    GetCustomers := TGetCustomers(SoapClient);
    For I := 0 To GetCustomers.Customers.Count - 1 Do
    Begin
        Customer := GetCustomers.Customers[I];
        ShowMessage(Customer.Name + ' : ' + Customer.EMail);
    End;
End;
 
Procedure Index.Button1Click(Event: TDOMEvent);
Var
    GetCustomers : TGetCustomers;
Begin
    GetCustomers := TGetCustomers.Create;
    GetCustomers.OnWebMethodReturn := @HandleGetCustomers;
    GetCustomers.Execute;
End;


Related Topics

Back to top