Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics Dr.Bob's Delphi Courseware Manuals
 Dr.Bob Examines... #122
See Also: Dr.Bob's Delphi Papers and Columns

Delphi Prism ASP.NET web chat service
Using Delphi 2010, we can build ASP.NET web services and projects, as I often demonstrate in my ASP.NET workshops and training events (in The Netherlands, UK and Sweden for example). In this article, I'll demonstrate how we can use Delphi Prism and ASP.NET to write a web chat service, consuming it in an ASP.NET web form as well.
In order to participate with the demo of this month, you need a copy of Delphi Prism. After starting Delphi Prism, do File | New Project, and in the Web node of the Delphi Prism project types, select the ASP.NET Web Application. Specify WebChat as name of the application. This will create a new solution with one project called WebChat. Inside the project, well have a Default.aspx file (with associated Default.aspx.pas and Default.designer.pas), a Global.asax file (with associated Global.asax.pas), a Web.config file and a number of References.
Well add the GUI controls to the Default.aspx file later, but lets first add the Web Service engine to our chat service. Right-click on the WebChat node in the Solution Explorer, and select Add | New Item. In the dialog that follows, select the Web node of the Oxygene categories, and pick the Web Service item. Set the name to chat.asmx and click on Add. This will generate a new file called chat.asmx with associated chat.asmx.pas.
Unfortunately, inside the generated source code in file chat.asmx.pas the [WebMethod] attribute still appears above the implementation of the example method chat.HelloWorld instead of the definition of this method in the chat class. We need to move that attribute from the implementation to the definition. In fact, we dont need the entire HelloWorld method at all, so feel free to remove both the definition and implementation.
For the chatroom example, I need two methods: one to send a chat message, and another to retrieve all chat messages.

    [WebMethod]
    method SendMessage(const User, Message: string);
    [WebMethod]
    method GetMessages: String;

ChatMessage
A chat message contains of three parts: a date + time, a username and a message. The date + time may not be that important, but the username is very helpful to identify who is saying what of course. The public ChatMessage class definition is as follows:

type
  ChatMessage = class
  public
    Time: DateTime;
    User: String;
    Message: String;
    constructor(const ATime: DateTime; const AUser, AMessage: String);
  end;

The purpose of the constructor is to fill the Time, User and Message fields of the ChatMessage, so we can quickly construct the ChatMessage and put it in a dynamic list of chat messages.

constructor ChatMessage(const ATime: DateTime; const AUser, AMessage: String);
begin
  Time := ATime;
  User := AUser;
  Message := AMessage
end;

The crux of the chatroom is the ASP.NET application object, which is shared by all connections, and the ideal storage facility for the messages in the chatroom. We need to define a key for the application object, which will be a constant, defined in the chat class:

type
  [WebService(&Namespace := 'http://eBob42.org/')]
  chat = public class(System.Web.Services.WebService)
  public
    const key = 'chatroom';

We will use the Application[key] as storage room for the collection of chat messages. A convenient container is the ArrayList, which supports an Add method to add a new ChatMessage, as well as an enumerator to walk through the list of chat messages again.

SendMessage
The main question for the SendMessage method is whether the application[key] already holds an ArrayList with one or more ChatMessages.

method chat.SendMessage(const User, Message: string);
var
  Messages: ArrayList;

begin
  if not Assigned(Application[key]) then
  begin
    Messages := new ArrayList();
    Application[key] := Messages
  end
  else Messages := Application[key] as ArrayList;

  Messages.Add(ChatMessage.Create(DateTime.Now, User, Message))
end;

GetMessages
Retrieving the messages from the application object and returning them as a nicely formatted string is done in the GetMessage method, implemented as follows:

  method chat.GetMessages(const Start: dateTime): String;
  begin
    Result := '';
    if Assigned(Application[key]) then
    begin
      var myEnum: IEnumerator := (Application[key] as ArrayList).GetEnumerator;
      while myEnum.MoveNext do
        Result := Result + (myEnum.Current as ChatMessage).Time.ToString + ' ' +
          (myEnum.Current as ChatMessage).User + ': ' +
          (myEnum.Current as ChatMessage).Message + #13#10
    end
  end;

Note that Im calling the GetEnumerator method of the ArrayList (retrieved from the Application[key]) and then use the MoveNext and Current methods of the enumerator to iterate through the chat messages.

More GetMessages
With this in mind, we can actually write two slightly more advanced GetMessages methods: one to retrieve only message of a certain user, and one to retrieve only messages from a certain time (from the last hour or day for example). This results in the following implementation of the first public chatroom example on my server:

type
  [WebService(&Namespace := 'http://eBob42.org/')]
  [WebServiceBinding(ConformsTo := WsiProfiles.BasicProfile1_1)]
  [System.ComponentModel.ToolboxItem(false)]
  chat = public class(System.Web.Services.WebService)
  public
    const key = 'chatroom';

    [WebMethod]
    method SendMessage(const User, Message: string);
    [WebMethod]
    method GetMessages(const Start: DateTime): String;
    [WebMethod]
    method GetUserMessages(const User: String): String;
  end;

The implementation of the GetMessages method is extended with a Start parameter of type DateTime. We can use this value to compare the Time of the ChatMessage in order to select only the chat messages that are more recent.

  method chat.GetMessages(const Start: DateTime): String;
  begin
    Result := '';
    if Assigned(Application[key]) then
    begin
      var myEnum: IEnumerator := (Application[key] as ArrayList).GetEnumerator;
      while myEnum.MoveNext do
        if (myEnum.Current as ChatMessage).Time > Start then
          Result := Result +
            (myEnum.Current as ChatMessage).Time.ToString('yyyy-MM-dd HH:mm:ss') + ' ' +
            (myEnum.Current as ChatMessage).User + ': ' +
            (myEnum.Current as ChatMessage).Message + #13#10
    end
  end;

This concludes the implementation of the web chat service - without any security, yet (that will be a story for another day - stay tuned).

ASP.NET Web Client
Let's import and consume the chat web service in the same Delphi Prism solution. Right-click on the References node in the Solution Explorer, and select Add Web Reference. This gives us a dialog where we have the following choices:

If we select the "Web services in this solution" option, we get a list of all web services that are found in this solution (obviously only one at this time, our chat web service):

If we select the chat web service, we see the URL http://localhost:2743/chat.asmx - running using the local web host. This is a nice URL to test, but not the real deployment URL, of course.

After deployment - and at the time of writing - the URL for the ASP.NET web chat service on my web server is as follows: http://www.bobswart.nl/webchat/chat.asmx.

If we import that URL, we get an import unit in the namespace WebChat.nl.bobswart.ww that we can use in the ASP.NET client application, for example in the Default.aspx file as follows:

  <%@ Page Language="Oxygene" AutoEventWireup="true" CodeBehind="Default.aspx.pas"
    Inherits="WebChat._Default" %>

  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

  <html xmlns="http://www.w3.org/1999/xhtml" >
  <head runat="server">
      <title>Dr.Bob's Chatroom</title>
  </head>
  <body>
      <form id="chatroom" runat="server">
      <div>
          <asp:TextBox ID="tbChat" runat="server" TextMode="MultiLine" Rows="20"
              Width="90%"></asp:TextBox>
          <hr />
          Name:
          <asp:TextBox ID="tbName" runat="server" Width="100px"></asp:TextBox>
          <br />
          Message:
          <asp:TextBox ID="tbMessage" runat="server" Width="80%"></asp:TextBox>
          <br />
          <asp:Button ID="btnSendMessage" runat="server" Text="Send Message"
              onclick="btnSendMessage_Click" />
          <asp:Button ID="btnGetNessages" runat="server" Text="Get Messages"
              onclick="btnGetNessages_Click" />
      </div>
      </form>
  </body>
  </html>

There are two event handlers that we need to define: for the btnSendMessage click (to send the contents of the tbName and tbMessage TextBox controls to the web service), and the btnGetMessages click (to retrieve all messages - for example the ones from the last 24 hours).
First, we need to add the WebChat.nl.bobswart.www namespace to the uses clause, and then we can create an instance of the chat web service proxy before we call the SendMessage method.

  implementation
  uses
    WebChat.nl.bobswart.www;

  method _Default.btnSendMessage_Click(sender: System.Object; e: System.EventArgs);
  var
    ws: chat;
  begin
    ws := new chat();
    ws.SendMessage(tbName.Text, tbMessage.Text);
  end;

Retrieving the current contents of the chat room (i.e. all messages since yesterday) can be done by calling the GetMessages method, passing DateTime.Now.AddDays(-1) as start parameter.

  method _Default.btnGetNessages_Click(sender: System.Object; e: System.EventArgs);
  var
    ws: chat;
  begin
    ws := new chat();
    tbChat.Text := ws.GetMessages(DateTime.Now.AddDays(-1))
  end;
Note that it might be a good idea to call the GetMessages method right after the call to SendMessage in the btnSendMessage_Click event handler, to ensure that the user immediately sees his own message appear in the chatroom.

A public version of this chatroom demo is now available on my server at http://www.bobswart.nl/webchat/. Feel free to leave messages and/or write your own client to this web service.

Note that I will add security in the form of SOAP Headers in the next few weeks (a bit sooner if the chat room is attached by spammers ;-)).

Summary
In this article, I've demonstrated how we can use Delphi Prism and ASP.NET to write a web chat service, consuming it in an ASP.NET web form as well. Next time, apart from SOAP Headers, I'll also add HTTPS support to the server to increase the secutiry of this otherwise public chatroom.


This webpage © 2010 by Bob Swart (aka Dr.Bob - www.drbob42.com).All Rights Reserved.