In this tutorial, we'll explore using cookies, hidden fields and the TPageProducer component. For a detailed explanation of what cookies are, have a look at the article - What are cookies?.
Use of Cookies
In this example, we will use cookies to store information about a user to be able to use this information when the user returns to the site at a later date. In this way, the user won't have to enter this information each time s/he comes to this part of the web site. In other words, the use of cookies in this example is not so much to "maintain state" as it is to retain information. But we would set cookies and read values in exactly the same way if we wanted to use cookies to maintain state during a session. So the concept will still work.
Use of Hidden Fields
Hidden fields in this case are used by way of maintaining session information. A very simple use, but none the less the way hidden fields will be used in most cases. Information about the project is passed on to an action using fat ULRs. In this action, we need to generate an HTML Data Entry form. When the form's submit button is clicked, we need to save this information along with the form's fields to a database. This information (Project Information) is required two steps later:
- First the Project information is sent to the action that generates the HTML Form.
- When the submit button of the form is clicked, this information is saved to a database table
Since we don't need to show the user this information, we use hidden fields in the generated HTML Form to store this information so we can use it later, at the time of posting this information.
We could have used cookies for this job as well. But remember that cookies are a limited resource (a certain number and size restriction) and also, that the user may have disabled cookies in his/her browser. Since this information is critical to the proper functioning of our application, we don't want to rely on cookies for this job.
The use of the TPageProducer component
The Data Entry HTML form we have will either be presented to the first time visitor as a blank form, with no values entered or, for repeat visitors whose information we already have, the form will be presented to the visitor pre populated. Rather than have two templates that will allow us to do this, we'll use the capabilities of the TPageProducer component to populate the fields with information extracted from cookies or leave them blank as the case may be. This example of the usage of the TPageProducer component should give you a good idea of its usage.
The Project
The project is a download registration form. This is the form you see each time you download a project from my web site (probably not implemented at this time). There are two fields of information in this HTML form:
- User Name
- Email Address
Figure 1 shows what the HTML form will look like for the first time visitor
Figure 1 Showing the HTML Form as a first time Visitor will see it
Once the user enters this information and clicks on the Download button, we save his/her information as cookies and then present him/her with the standard download screen to be able to download the project files. The next time this visitor downloads a project file, either in the same session or on another day, s/he will be presented with the same form. Only this time the information will be filled out automatically by our ISAPI application as shown in Figure 2.
Figure 2 Showing a pre populated HTML Form using Cookie information
Since the Download registration form is common for all downloads, we need to have information about the project that the user is interested in, so as to present him with the correct files to download. We do this by calling our ISAPI's action with information in the URL. We can extract this information using the Response object's QueryFields property. This information is later used to get the name of the file that needs to be sent as well as to put the Project title in the Download registration form.
Getting Started
In this tutorial, I assume you've read the earlier tutorials, so I'm not going into the details of how to start an ISAPI project etc. From now on, I'll just list of the steps that need to be taken. I assume, when I say, "Change the output directory of the project to your web server's scripts folder", you know what I mean by that, why we need to do it, and how to do it.
- Start with an ISAPI project
- Create two action items and name them:
- waDownLoadReg with a PathInfo of /DownloadReg
- waRegister with a PathInfo of /Register
- Place a TPageProducer component on the Web DataModule.
- Change the output directory of the project to your web server's scripts folder(C:\inetpub\scripts)
- Save the project as Download.dpr
The Web DataModule should look like that shown in Figure 3.

Figure 3 showing the Web DataModule as it should look like up to now
In the OnAction event of our first action item - waDownloadReg, we need to send back the HTML form shown in Figure 1 above. But we also need to know the Tutorial details so we know which tutorial the user is interested in and show the Tutorial Title in the form. In the Tutorials section of my web site, you will have noticed that at the end of each project, there is a link to download the project files. It is this link that will call this action. The way my data model is structured, we need to know the TutorialType and TutorialCode field values. These are the key fields (composite key) of the table that contains the information for each Tutorial, such as:
- Tutorial Title
- Content (the text of the whole tutorial)
- ProjectFile (the name of the zip file for this project
And other information that is relevant to the tutorial. For this tutorial the: TutorialType = DelphiISAPI
TutorialCode = ISAPICookiesHiddenFields
These two fields will uniquely identify each Tutorial in my database. The HTML of the link you see at the end of each project looks like this: <A HREF="/scripts/website.dll/DownloadReg?TutorialType=DelphiISAPI&TutorialCode= ISAPICookiesHiddenFields">Download Project</A>
Website.dll is the main ISAPI that runs my web site. The Action - waDownloadReg gets two parameters sent to it. These two parameters will tell us what tutorial the user is interested in. This information was generated when you first clicked on the link to this project from the main Tutorials section to be used later in an other action, we've "maintained the state" using fat URLs. The OnAction event of the action waDownloadReg looks like this:
procedure TWebModule1.WebModule1waDownLoadRegAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
{ This action expects to see the Project Key as Query Fields }
PageProducer1.HTMLDoc.Text := GetDownloadRegistrationForm;
Response.Content := PageProducer1.Content;
end;
The function GetDownloadRegistrationForm is our "template" for the form shown in Figure 1. We could have used the physical .htm file for this purpose if we wanted. The advantage to doing that is that you could change the look and feel of the form without having to recompile the project since the file resides physically on the hard drive. I personally prefer to have my projects less dependent on external files. But say you wanted to give your client the option to modify the look and feel, you'd be better off using an external .htm file making sure, the client understands the special "transparent" tags that we use for the TPageProducer.
So first we assign the result of the function GetDownloadRegistrationForm to the HTMLDoc property of the TPageProducer component and then assign the Content property of the TPageProducer component to the Response object's Content property. This will in turn send back the HTML form shown in Figure 1.
The function GetDownloadRegistrationForm looks like this.
function TWebModule1.GetDownloadRegistrationForm : string;
begin
{ Template for the Download Registration Form }
Result :=
'<HTML>' crlf
' <HEAD>' crlf
'<#METATAGS>' crlf
' <TITLE>Download Registration Form</TITLE>' crlf
' </HEAD>' crlf
'<BODY>' crlf
'<#Form>' crlf
'<#Tutorial Field=TutorialType>' crlf
'<#Tutorial Field=TutorialCode>' crlf
'<TABLE BORDER="1" BORDERCOLOR="#66ccff" BGCOLOR="#6699cc" CELLPADDING="2">' crlf
'<TH BGCOLOR= "#ccccff" COLSPAN="2"><CENTER>Download Registration Form</CENTER></TH>' crlf
' <TR BGCOLOR= "#6699cc">' crlf
' <TD COLSPAN="2"><CENTER><H3><#ProjectName></H3></CENTER></TD>' crlf
' </TR>' crlf
' <TR BGCOLOR= "#6699cc">' crlf
' <TD COLSPAN="2"><#FileName></TD>' crlf
' </TR>' crlf
' <TR BGCOLOR="#ccccff">' crlf
' <TD>Name</TD>' crlf
' <TD><#DataEntry Field=Name FieldName=UserName></TD>' crlf
' </TR>' crlf
' <TR BGCOLOR="#ffffcc">' crlf
' <TD>Email Address</TD>' crlf
' <TD><#DataEntry Field=Email FieldName=UserEmail></TD>' crlf
' </TR>' crlf
' <TR>' crlf
' <TD><INPUT TYPE="SUBMIT" VALUE="Download"></TD>' crlf
' <TD><INPUT TYPE="RESET" VALUE="Reset"></TD>' crlf
' </TR>' crlf
'</TABLE>' crlf
'</FORM>' crlf
'</BODY>' crlf
'</HTML>' crlf;
end;
This function returns standard HTML that will generate the download registration form. There are some special tags however, that need some explanation. These are tags that start with <#….>. These are called Transparent Tags. Browsers are supposed to ignore these tags. But the TPageProducer component generates an OnHTMLTag event for each transparent tag it encounters while parsing the HTML when the Content property is accessed (assigned).
The OnHTMLTag event looks like this:
procedure TWebModule1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
const TagString: String; TagParams: TStrings; var ReplaceText: String);
begin
if TagString = 'METATAGS' then
ReplaceText := ' <LINK REL=STYLESHEET TYPE="text/css" href="http://www.matlus.com/home-styles.css">';
if TagString = 'ProjectName' then
ReplaceText := GetProjectTitle(Request.QueryFields.Values['TutorialType'],
Request.QueryFields.Values['TutorialCode']);
if TagString = 'FileName' then
ReplaceText := Format('<INPUT TYPE="HIDDEN" NAME="txtFileName" VALUE="%s">',[ProjectFileName]);
if TagString = 'Form' then
ReplaceText := Format('<FORM ACTION=%s/Register METHOD="POST">',
[Request.ScriptName]);
if TagString = 'Tutorial' then
ReplaceText := Format('<INPUT TYPE="HIDDEN" NAME="txt%s" VALUE="%s">',
[TagParams.Values['Field'],
Request.QueryFields.Values[TagParams.Values['Field']]]);
if TagString = 'DataEntry' then
ReplaceText := Format('<INPUT TYPE="TEXT" NAME="txt%s" VALUE="%s">',
[TagParams.Values['Field'], GetCookie(TagParams.Values['FieldName'])]);
end;
Notice the parameters that are passed to us in this event. Notice also, that the parameter ReplaceText is a var parameter.
The TPageProducer component
Each time the Content property of the TPageProducer component is accessed (assigned) the OnHTMLTag event is fired for every Transparent tag found. The TagString parameter gives us the HTML Tag name of the transparent tag. For instance, our first transparent tag in our template is <#METATAGS>. The TagString parameter will contain the value METATAGS.
In the OnHTML event handler you will notice that we check for every Tag name using code like:
if TagString = 'METATAGS' then
ReplaceText := ' <LINK REL=STYLESHEET TYPE="text/css" href="http://www.matlus.com/home-styles.css">';
In this case we replace the transparent tag (<#METATAGS>) with the string literal ' <LINK REL=STYLESHEET TYPE="text/css" href="http://www.matlus.com/home-styles.css">';
in other words, the string <#METATAGS> is replaced with the string ' <LINK REL=STYLESHEET TYPE="text/css" href="http://www.matlus.com/home-styles.css">';, including the "#" and the "<" and ">". The tags "<…>" are just placeholders. So even though we check for just the tag name (METATAGS), we replace the whole tag.
You will have noticed that some of the transparent tags don't have just names. For instance, the transparent tag <#Tutorial Field=TutorialType> has more than just the name. The tag name in this case is Tutorial.
Each transparent tag can have parameters. Parameters are in the form Name=Value. It is important to know that the delimiter for parameters is a space. So you can't have a parameter whose name part or value part contains a space. This makes the usage of parameters in transparent tags a bit tricky. But none the less, it's the parameters that make using the transparent tags in conjunction with the TPageProducer extremely useful. The parameters of an HTML Transparent tag are surfaced to us in the OnHTMLTag event as TStrings, namely the TagParams parameter.
Using the Values property of the TStrings object we can extract the value of any parameter for a given transparent tag. Based on the value of certain TagParams, we can process the result differently. This capability can prove to be extremely powerful and useful. In this tutorial, I've tried to show how we can make good use of this particular capability of the TPageProducer.
Let us now examine this more closely. Below, is a snippet of code from the OnHTMLTag event that processes the Tutorial tag:
if TagString = 'Tutorial' then
ReplaceText := Format('<INPUT TYPE="HIDDEN" NAME="txt%s" VALUE="%s">',
[TagParams.Values['Field'],
Request.QueryFields.Values[TagParams.Values['Field']]]);
To start with, you will notice that it is this tag that is responsible for creating the Hidden fields of our HTML Form. These hidden fields contain the TutorialType and TutorialCode values that were passed to us via the QueryString property of the Request object. Remember the example of a URL that will call our ISAPI? <A HREF="/scripts/website.dll/DownloadReg?TutorialType=DelphiISAPI&TutorialCode= ISAPICookiesHiddenFields">Download Project</A>
Notice the TutorialType=DelphiISAPI
TutorialCode=ISAPICookiesHiddenFields
The values for these two parameters need to be passed on to the action that will submit our HTML form. We're using Fat URLs here to maintain state or rather persist information. But since this information is required two actions later, we need to persist this information over two actions. The first action generates the HTML form, so we persist this information in hidden fields of the HTML form to be used when the form is later submitted. I hope this is clear. It can be a bit confusing at first. Figure 4 shows the various steps involved in this process.
Figure 4 showing the persistence of information across multiple actions
Getting back to the hidden fields and transparent tags …
There are 2 tags named Tutorial in our template. Both have a parameter called Field. The first tag's Field parameter's value is TutorialType and the second tag's Field parameter's value is TutorialCode.
The resulting HTML string in the OnHTMLTag event for the first tag will be:
<INPUT TYPE="HIDDEN" NAME="txtTutorialType" VALUE="DelphiISAPI">
The resulting HTML string for the second tag will be: <INPUT TYPE="HIDDEN" NAME="txtTutorialCode" VALUE="ISAPICookiesHiddenFields">
Similarly, you will have noticed that the DataEntry tag appears twice in our template. The code that will process these tags in the OnHTMLTag event looks like this:
if TagString = 'DataEntry' then
ReplaceText := Format('<INPUT TYPE="TEXT" NAME="txt%s" VALUE="%s">',
[TagParams.Values['Field'], GetCookie(TagParams.Values['FieldName'])]);
The GetCookie function returns the value of a cookie given a name (name=value pair). We'll examine cookies later, but for now just understand that this function returns the value of a given cookie.
Before I go on to explain the processing of this tag, imagine if you will, that we have an HTML data entry form. Imagine also, that we'd like to use this form not only for data entry, but also to edit/modify existing data. We can have one template such as the one we have, that generates the HTML form the way we want it. Using the TPageProducer component and transparent tags in this way we could present a blank form for data entry or a pre-populated form for data edits. This comes real handy when doing data intensive ISAPI work. Of course, we'd need to know if we wanted to construct the HTML form with blank fields or with pre-populated fields.
For example:
if TagString = 'DataEntry' then
begin
if InEditMode then
ReplaceText := Format('<INPUT TYPE="TEXT" NAME="txt%s" VALUE="%s">',
[TagParams.Values['Field'], Table1.FieldByName(TagParams.Values['FieldName'])])
else
ReplaceText := Format('<INPUT TYPE="TEXT" NAME="txt%s" VALUE="">',
[TagParams.Values['Field']]);
end;
Where InEditMode is a Boolean variable that will switch states to indicate the operation. But the key point here is the parameter FieldName that can be used for all fields of our database table! One transparent HTML tag with a parameter whose value indicates the field we're trying to construct.
This is exactly how we use the DataEntry tag here. If we find a cookie for the given name, we populate the HTML field with its value. If not, we leave it blank. So if the visitor is a first time visitor, s/he gets a blank form, if not s/he gets a pre-populated form.
Setting Cookies
Setting Cookies in Delphi using the WebBroker technology is really easy. There are a few things that one needs to be careful of, but once you've made a note of them, things should be really simple.
The Response object has a method called SetCookieFields that looks like this:
procedure TWebResponse.SetCookieField(Values: TStrings; const ADomain,
APath: string; AExpires: TDateTime; ASecure: Boolean);
It is recommended that you read the article - What are Cookies for an explanation of the various parameters of this method. We'll continue on from the end of that article here in our discussion of cookies and how they can be implemented in Delphi. The SetCookies procedure in our project looks like this:
procedure TWebModule1.SetCookies(CookieValues : array of string);
var
CookieVals : TStringList;
i : Integer;
begin
CookieVals := TStringList.Create;
try
for i := 0 to High(CookieValues) do
CookieVals.Add(CookieValues[i]);
Response.SetCookieField(CookieVals,Request.Host,Request.ScriptName,Now 30,False);
finally
CookieVals.Free;
end;
end;
Using a method such as this simplifies the process of setting cookies. The GetCookie method uses the Request object instead of the Response object. This method is a one liner and looks like this:
function TWebModule1.GetCookie(sName : string) : string;
begin
Result := Request.CookieFields.Values[sName];
end;
Now lets look at the /Register action of our project. This is the action that the HTML Form will call when the Submit button is clicked.
procedure TWebModule1.WebModule1waRegisterAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
ErrMSg : string;
UserName : string;
UserEmail : string;
TutorialType : string;
TutorialCode : string;
begin
{ Extract Values from the HTML Form's Fields }
UserName := Request.ContentFields.Values['txtName'];
UserEmail := Request.ContentFields.Values['txtEmail'];
TutorialType := Request.ContentFields.Values['txtTutorialType'];
TutorialCode := Request.ContentFields.Values['txtTutorialCode'];
{ Set the Cookies now }
SetCookies(['UserName=' UserName,'UserEmail=' UserEmail]);
{ Log the infomration to the database table }
if PostDataToDatabase(ErrMsg, UserEmail, TutorialType, TutorialCode, UserName) then
{ Everything went OK. Let the user download the Project Files }
Response.Content := SendFTPDialog
else
Response.Content := SendErrorPage(ErrMsg);
end;
First we extract the Form's field values into local variables so we can work with them directly. Once we have this information stored in variables, we Set the Cookies. In this project, we're interested in setting only 2 cookies. UserName=Value
UserEmail=Value
In large applications, where a lot of cookies are being set, we might resort to shorter cookies (ue to size limitation) or better yet, just a unique identifier (sessionID) that we can use to then lookup our database to find associated information. But in this project, we should be fine with the above cookies.
Once we've set the cookies, we need to log this rest of the Form's information and show our visitor the next screen. From here on out, the process it specific to this project. Those interested in this part of the project should look at the source code. We'll continue on to learn more about cookie in this article.
Session Cookies
In the above example, we set the cookies to expire in about 30 days.
Response.SetCookieField(CookieVals,Request.Host,Request.ScriptName,Now 30,False);
Session cookies are cookies that expire as soon as the visitor closes his browser. Sometimes, we'd like to set session cookies in our ISAPI applications and the way we can do this is by setting the variable AExpires to -1.
Cookies and Domains
Over and above what was explained in the article - What are Cookies, lets see some of the things we need to be careful of while developing/debugging our ISAPI applications. We've used the Request.Host variable as the value to be fed for the ADomain parameter of the SetCookieFields method of the Request object. This will evaluate to the host header value of the HTTP request. In other words, this will be the host part of the URL you specified while you called your ISAPI using the browser. What you need to be careful of here while testing is that you can't switch between using the IP address in the URL and the host name /machine name. For example, using localhost instead of 127.0.0.1 (loop back IP) is not the same. All of this can get quite confusing during testing. I strongly recommend getting into the habit of using the IP address of your machine in the URL for all you ISAPI work. Use either 127.0.0.1 or the actual IP address of your machine if you work in a LAN environment (where the IP address most probably will not change).
The Machine I develop on has a domain name registered to it. It also has an IP address allocated by my ISP and the loop back IP address and of course localhost. Because we are using the Request.Host property this property Indicates the value of the Host header of the HTTP request message. That is it extracts the Host header from the HTTP request. The cookie, therefore will be set for that particular "domain". The URL http://127.0.0.1/scripts/download.dll... will save a cookie for the ADomain - 127.0.0.1. This cookie will not be available for matlus.matlus.com, which is the domain name of my development machine. Nor will it be available for 24.28.203.177, which is the IP address of the same machine. You could spend hours if not days, if you're not careful with the way you define the URL in your browser. Stick to one and use only that for all your ISAPI work. There will be 3 different cookie files created if you use all three URLs for testing. The results can throw you in a loop!