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.
| BX Code |
|---|
/* %MA STATIC */ Imports SystemSerialiser Imports SystemWebMethod Namespace Module1 Public Class TLoginCredentials Inherits TSerializable Public Username As String Public Password As String End Class Public Class SecureWebMethod Inherits WebMethod Public LoginCredentials As TLoginCredentials :{"WSPublished=True", "WSFieldKind=In", "WSHeader=True"} Public Overrides Sub ExecuteActions If Not LoginCredentials.Username.Equals("username") Or Not LoginCredentials.Password.Equals("password") Then Throw New Exception("invalid username/password") End Sub End Class :{"WSAbstract=True"} Public Class CheckForNewMessages Inherits SecureWebMethod Public FoundNewMessages As Boolean :{"WSPublished=True", "WSFieldKind=Out"} Public Overrides Sub Execute FoundNewMessages = False End Sub End Class :{"WSPublished=True"} Public Class DeleteMessagesInTrash Inherits SecureWebMethod Public Overrides Sub Execute End Sub End Class :{"WSPublished=True"} End Namespace |
| CX Code |
|---|
/* $MA STATIC */ imports SystemSerialiser; imports SystemWebMethod; namespace Module1 { public class TLoginCredentials : TSerializable { public String Username; public String Password; } public class SecureWebMethod : WebMethod { public TLoginCredentials LoginCredentials; ["WSPublished=True", "WSFieldKind=In", "WSHeader=True"] public override void ExecuteActions() { if (!LoginCredentials.Username.Equals("username") || !LoginCredentials.Password.Equals("password")) throw new Exception("invalid username/password"); } } ["WSAbstract=True"] public class CheckForNewMessages : SecureWebMethod { public Boolean FoundNewMessages; ["WSPublished=True", "WSFieldKind=Out"] public override void Execute() { FoundNewMessages = False; } } ["WSPublished=True"] public class DeleteMessagesInTrash : SecureWebMethod { public override void Execute() { } } ["WSPublished=True"] } |
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.
| BX Code |
|---|
/* %MA DYNAMIC */ Imports SystemRPC Imports SystemInternalServices Namespace Index Public Class Index Inherits Form Published Button1 As Button Published Button2 As Button Published TextEdit1 As TextEdit Published TextEdit2 As TextEdit ' Public declarations Public Sub OnWebMethodReturn(SoapClient As TSoapClient) If Ctype(SoapClient, TCheckForNewMessages).FaultCode <> "" Then ShowMessage("The following error was encountered:\n" & Ctype(SoapClient, TCheckForNewMessages).FaultDetails) ElseIf Ctype(SoapClient, TCheckForNewMessages).FoundNewMessages Then ShowMessage("You have new mail.") Else ShowMessage("You have no new mail.") End If End Sub Published Message Sub Button1Click(Event As TDOMEvent) Dim CheckForNewMessages As TCheckForNewMessages CheckForNewMessages = New TCheckForNewMessages() CheckForNewMessages.LoginCredentials.Username = TextEdit1.Text CheckForNewMessages.LoginCredentials.Password = TextEdit2.Text CheckForNewMessages.OnWebMethodReturn = Ref Me.OnWebMethodReturn CheckForNewMessages.Execute() End Sub End Class End Namespace |
| CX Code |
|---|
/* $MA DYNAMIC */ imports SystemRPC; imports SystemInternalServices; namespace Index { public class Index : Form { published Button Button1; published Button Button2; published TextEdit TextEdit1; published TextEdit TextEdit2; // Public declarations public void OnWebMethodReturn(TSoapClient SoapClient) { if ((TCheckForNewMessages)(SoapClient).FaultCode != "") ShowMessage("The following error was encountered:\\n" + (TCheckForNewMessages)(SoapClient).FaultDetails); else if ((TCheckForNewMessages)(SoapClient).FoundNewMessages) ShowMessage("You have new mail."); else ShowMessage("You have no new mail."); } published message void Button1Click(TDOMEvent Event) { TCheckForNewMessages CheckForNewMessages; CheckForNewMessages = new TCheckForNewMessages(); CheckForNewMessages.LoginCredentials.Username = TextEdit1.Text; CheckForNewMessages.LoginCredentials.Password = TextEdit2.Text; CheckForNewMessages.OnWebMethodReturn = &this.OnWebMethodReturn; CheckForNewMessages.Execute(); } } } |
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;
| BX Code |
|---|
Public Class TCustomer Inherits TSerializable Public Name As String Public EMail As String End Class Public Class GetCustomers Inherits WebMethod Public Customers As List Of TCustomer :{"WSPublished=True", "WSFieldKind=Out"} Public Overrides Sub Execute Dim Customer As TCustomer Customer = New TCustomer() Customer.Name = "Alice" Customer.EMail = "alice@wonderland.com" Customers.add(Customer) Customer = New TCustomer() Customer.Name = "Bob" Customer.EMail = "bob@somewhere.com" Customers.add(Customer) End Sub End Class :{"WSPublished=True"} |
| CX Code |
|---|
public class TCustomer : TSerializable { public String Name; public String EMail; } public class GetCustomers : WebMethod { public list of TCustomer Customers; ["WSPublished=True", "WSFieldKind=Out"] public override void Execute() { TCustomer Customer; Customer = new TCustomer(); Customer.Name = "Alice"; Customer.EMail = "alice@wonderland.com"; Customers.add(Customer); Customer = new TCustomer(); Customer.Name = "Bob"; Customer.EMail = "bob@somewhere.com"; Customers.add(Customer); } } ["WSPublished=True"] |
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;
| BX Code |
|---|
Imports SystemRPC Imports SystemInternalServices ... Public Sub HandleGetCustomers(SoapClient As TSoapClient) Dim GetCustomers As TGetCustomers Dim Customer As TCustomer Dim I As Integer GetCustomers = Ctype(SoapClient, TGetCustomers) For I = 0 To GetCustomers.Customers.count - 1 Customer = GetCustomers.Customers(I) ShowMessage(Customer.Name + " : " + Customer.EMail) Next I End Sub Published Message Sub Button1Click(Event As TDOMEvent) Dim GetCustomers As TGetCustomers GetCustomers = New TGetCustomers() GetCustomers.OnWebMethodReturn = Ref HandleGetCustomers GetCustomers.Execute() End Sub |
| CX Code |
|---|
imports SystemRPC; imports SystemInternalServices; ... public void HandleGetCustomers(TSoapClient SoapClient) { TGetCustomers GetCustomers; TCustomer Customer; Integer I; GetCustomers = (TGetCustomers)(SoapClient); for (I = 0; I <= GetCustomers.Customers.count - 1; I++) { Customer = GetCustomers.Customers[I]; ShowMessage(Customer.Name + " : " + Customer.EMail); } } published message void Button1Click(TDOMEvent Event) { TGetCustomers GetCustomers; GetCustomers = new TGetCustomers(); GetCustomers.OnWebMethodReturn = &HandleGetCustomers; GetCustomers.Execute(); } |
Related Topics
- What are Web Services and why do I need to use them?
- Understanding the way Web Services are used in Morfik
- Creating Web Methods and using them in your application
- Importing and Consuming external Web Services

