Home  ==>  papers  ==>  web  ==>  asp_net  ==>  delphi_asp_net_portal_programming   

Delphi ASP.NET Portal Programming - Felix John COLIBRI.

1 - The Delphi Asp.Net Web Portal

1.1 - Purpose

The goal of a Portal is to offer a framework which can be used to build Web pages having the same look an feel and which can be customized using .HTML pages included in the Portal.

We are going to present the Asp.Net Starter Kit portal, in a Delphi version.

Here is a snapshot of the basic Starter Kit page:


1.2 - The Asp.Net Starter Kit

The Asp.Net Portal comes bundled with a tutorial module, as well as many .HTML files explaining how to use the portal, from an administrator point of view. There are also .HMTL pages of the source code, which can be displayed by directly looking at the .CS or .ASPX/ASCX files anyway.

This paper will explain how we handled the project.

1.3 - What was changed

The basic "Asp.Net Starter Kit" files are
  • in C# language
  • for ASP.Net version 1
  • the database is Sql Server, with all SQL scripts included, and the binary file
  • the web server is IIS (Internet Information Server)
We basically started off with the Delphi translation made by Pascal Chapuis, which used
  • Delphi 8 or Delphi 2005
  • either Firebird, or Sql Server. For Firebird, the FBK backup file is provided, for Sql Server the full Sql scripts are included
  • the access components are the Firebird Data Provider, or the Sql Data Provider
  • the web server is IIS or Cassini
This version was transformed in the following way
  • we are using Delphi 2006
  • the Database is Interbase 6, or 7.5, with the SQL scripts as well as the binary file
  • the access component set is the BDP (Borland Data Provider)
  • the Web Server is CASSINI
  • Asp.Net is version 1 or 2

1.4 - The strategy

Here is a summary of our modifications
  • we renamed many of the files and identifiers
  • we created some additional folders
  • we removed the Mobile part
  • we commented out the security and Asp.Net cache part

1.5 - The File Organization

We will use the following folders:



  • _data: contains the Interbase database file, and the SQL scripts
  • _exe: contains the intermediate compilation files (.DCUIL) and our logs (MUST be in the same folder as the .BDSPROJ)
  • 1_helpers: the configuration units, the security handling, and some global helper units (litteral strings etc)
  • 2_db_components: most of the units making a connection to the database (with the exception of U_C_CONFIGURATION)
  • 3_elements: all components and pages other than the main a_asp_portal.ASPX
    • the common files: the portal banner, the Module title, the Module ancestor CLASS
    • 31_user_components:
      • all ASCX Module components
      • 311_edit_user: the ASPX Module edition pages
    • 32_admin_components
      • the ASCX Modules for administering the portal (changing the portal configuration)
      • 321_edit_admin the ASPX pages for portal administration
  • bin: Asp.Net JIT compiled code. DO NOT change anything there
  • Data: the files used by the "About the Portal" tab (presentation and tutorial about administering the portal)
  • images: the .GIF used by the different .ASPX and .ASCX files
  • portal_images: our own logo, placed in the banner
Note that there is also a hidden "__history" folders created and used by Delphi, as well as hidden files with ~nnn~ extensions, all handled by Delphi

1.6 - The detail of the project

We will now present
  • the programming architecture
  • the database
  • the configuration files
  • the pages used by the users
  • the administration of the portal

2 - The Overall Web Portal Architecture

As explained in the Delphi Asp.Net Portal architecture paper, the user sees the Portal as several different pages. Each pages includes
  • a banner
  • several tabs, like in a NoteBook, one of them being selected
  • several modules
Such pages are called, in Portal parlance, "views".

From a programming viewpoint, the views are assembled using the following elements:

  • the main frame is the a_asp_portal.aspx template, with
    • an instance of the a_c_desktop_portal_banner.ascx Asp.Net component
    • an .HTML Table with three cells, called panes, where the modules will be stuffed
    Here is the organization:


  • the a_c_desktop_portal_banner.ascx Asp.Net component contains
    • the site icon
    • the site title
    • links to the home page and the index
    • more important, a DataList which will be used for the tabs
    The banner looks like this:


  • each Module is an .ASCX Asp.Net component which has
    • some kind of header. It can be a simple Label, but in most case it is an a_c_desktop_module_title.ascx Asp.Net component with a Label and an Hiperlink (for calling a Module edit page)
    • some content control (a DataGrid, a DataList, or some TextBoxes)
    Here is the a_c_quicklinks.ascx Module (no module title) and the a_c_html_module.ascx Module (with an a_c_desktop_module_title.ascx module title):


    Since all Modules must manage some identification parameters common to all Modules, an abstract c_portal_module_control ancestor class has been created with attributes for ModuleId, PortalConfiguration etc.
    So all Module CLASSes (like TWebUserControlContacts, for instance) descend from this common CLASS.

    The HyperLink control placed on the Module title is used do call an edit page associated with the module. For instance, clicking on the "Edit" hyperlink of an a_c_announcements.ascx will request and display the the a_edit_announcements.aspx page (which contains TextBoxes, Validators, Buttons etc). The Edit page still has a banner (to keep the same look), but without any tabs.

    This can be represented like this:


A complete view, with initialized tabs and some modules, looks like this:


In addition to the usual Asp.Net code files (.PAS,.ASPX, .ASCX), the Portal also uses configuration and content data files:

  • the structure files:
    • a ASPNETPORTAL.CSS Cascading Style Sheet for the font and graphic properties
    • all the images (the arrows, the Asp.Net Logo etc)
    • x_asp_portal.xml which contains the structure and properties of each view (basically what Module is contained in each view, and in which Pane it is displayed) and module definitions (the file name of each Module)
  • the content files:
    • the Interbase database, whith the content of the Modules (the .HTML text of the a_c_html_module.ascx Module, the list of discussions from the a_c_discussion.ascx Module etc)
    • an .XML/XSD sample SALES.XML file to show how to include .XML in the a_c_xml.ascx Module
    • image (.GIF) wich are displayed in some Modules, mainly in the a_c_html_module.ascx .HTML content
This can be represented like this:


We will now present in more detail

  • the Interbase Database
  • the configuration file
  • the User pages
  • the Administration pages

3 - The Delphi Web Portal Database

3.1 - The Database Schema

The database schema is the following:


Note that

  • only 9 of the 16 Modules have database content. For the other Modules
    • a_c_xml.ascx uses an .XML file
    • a_c_image.ascx uses .GIFs
    • the other modules manage in memory data
  • the portal_useroles is an N-N link table

3.2 - Database Creation

We have provided the Sql script which enables the Interbase database creation. In addition, we have included the .ZIP of the complete Interbase 6 and Interbase 7.5 database.

3.3 - Ado.Net and the ConnectionString

We will use Ado.Net to transfer data to and from the Database.

Ado.Net requires a ConnectionString, which contains the detail of the driver, the .Net assembly, the file location etc.

This connection string is set in the Web.Config global file:

<?xml version="1.0" encoding="utf-8" ?>
  <!-- application specific settings -->
      <add key="ConnectionString" value="assembly=Borland.Data.Interbase,Version=,
      <add key="ConnectionOptions" value="waitonlocks=False;commitretain=False;
" />

So fetching the connection string will be performed using:

var my_connection_stringString;
  my_connection_string:= ConfigurationSettings.AppSettings.Get('ConnectionString');

3.4 - The data table access classes

To fetch or modify the content of the tables, separate CLASSes have been defined containing the main method for handling the access. For instance, for handling contacts, the U_C_CONTACTS_DB contains the following CLASS:

Unit u_c_contact_db;
    uses system.Configurationsystem.Data
        , Borland.Data.Provider
        , Borland.Data.Common

    type ContactsDB=
             constructor Create;

             function GetContacts(moduleIdInteger): DataSet;
             function GetSingleContact(itemIdInteger): BdpDataReader;

             function AddContact(moduleIdIntegeritemIdInteger;
                   Widestringcontact2Widestring): Integer;
             procedure DeleteContact(itemIDInteger);
             procedure UpdateContact(moduleIdIntegeritemId:
         end// ContactsDB


3.5 - Access to the Database

To access those tables, we chose to use the BDP. We had not much choice anyway, since this is the most obvious way to access Interbase from Delphi 2006.

In the basic product, nearly all requests use stored procedures. So our Interbase database contains those stored procedures.

Here is the Get_Events stored procedure:

      pv_item_id INTEGER,
      pv_created_by_user VARCHAR(100),
      pv_created_date TIMESTAMP,
      pv_title VARCHAR(150),
      pv_where_when VARCHAR(150),
      pv_description VARCHAR(2000),
      pv_expire_date TIMESTAMP
    FROM Portal_Events
        (ModuleID = :p_module_id)
        (ExpireDate > Current_timestamp)

Using the Sql Adapter components, this procedure could be called with:

function EventsDB.f_c_get_events(p_module_idInteger): DataSet;
  var l_c_sql_connectionSqlConnection;
    l_c_sql_connection:= SqlConnection.Create(k_configuration_string);
    l_c_sql_transaction:= l_c_sql_connection.BeginTransaction;

    l_c_sql_adapter:= SqlDataAdapter.Create('Portal_GetEvents'l_c_sql_connection);
    l_c_sql_adapter.SelectCommand.Transaction:= l_c_sql_transaction;
    l_c_sql_adapter.SelectCommand.CommandType:= CommandType.StoredProcedure;

    l_c_sql_parameter1:= SqlParameter.Create('@ModuleID'SqlDbType.Integer, 4);
    l_c_sql_parameter1.Value:= system.object(p_module_id);

    l_c_dataset:= DataSet.Create;
    result:= l_c_dataset;
  end// f_c_get_events

With the BPD, it seems that we have to initialize ALL parameters (not only the INPUT parameters). Adding a parameters is performed with 3 lines (which certainly could be concatenated). In addition starting the request always uses the same calls. So we created the following helper functions:

  • to start the connection:

    const k_connection_string'xxx...';
    var g_open_bdp_connection_countInteger= 0;

    procedure open_bdp_connection(p_c_bdp_connectionBdpConnection);
      end// open_bdp_connection

    procedure open_portal_bdp_connection(var pv_c_bdp_connectionBdpConnection);
        pv_c_bdp_connection:= BdpConnection.Create(k_connection_string);

      end// open_portal_bdp_connection

    procedure open_portal_bdp_connection_transaction(
        var pv_c_bdp_connectionBdpConnection;
        var pv_c_bdp_transactionBdpTransaction);
        if g_open_bdp_connection_count<= 0
          then pv_c_bdp_transaction:= pv_c_bdp_connection.Begintransaction
          else pv_c_bdp_transaction:= Nil;
      end// open_portal_bdp_connection_transaction

  • to initialize the Stored Procedure parameters:

    function f_c_add_input_parameter(p_c_bdp_commandBdpCommand;
        p_valueSystem.Object): BdpParameter;
        Result:= BdpParameter.Create(p_namep_typep_size);
        Result.Direction:= ParameterDirection.Input;
        Result.Value:= p_value;

      end// add_input_parameter

    function f_c_add_output_parameter(p_c_bdp_commandBdpCommand;
      p_sizep_max_precisionInteger): BdpParameter;
      Result:= BdpParameter.Create(p_namep_typep_size);
      Result.MaxPrecision:= p_max_precision;
      Result.Direction:= ParameterDirection.Output;

    end// add_output_parameter

And our Get_Events stored procedure is called with:

function EventsDB.GetEvents(p_module_idInteger): DataSet;
  var l_c_bdp_connectionBdpConnection;

    l_c_bdp_dataadapter:= BdpDataAdapter.Create('Portal_GetEvents'l_c_bdp_connection);
    l_c_bdp_dataadapter.SelectCommand.Transaction:= l_c_bdp_transaction;
    l_c_bdp_dataadapter.SelectCommand.CommandType:= CommandType.StoredProcedure;

        BdpType.Int32BdpType.Int32, 4, System.Object(p_module_id));

        BdpType.Int32BdpType.Int32, 4, 4);
        BdpType.StringBdpType.String, 100, 100);
        BdpType.DateTimeBdpType.DateTime, 16, 16);
        BdpType.StringBdpType.String, 150, 150);
        BdpType.StringBdpType.String, 150, 150);
        BdpType.StringBdpType.String, 2000, 2000);
        BdpType.DateTimeBdpType.DateTime, 16, 16);

    l_c_dataset:= DataSet.Create;

    result:= l_c_dataset;

  end// get_with_stored_procedure

In fact the code in the .ZIP uses routines with optional loging functionalities (with journal inputs for database connection, connection count, results etc), which helped us debug the database access.

Using those debugging functions, we also found out that the BDP Stored Procedures did not work as expected. At least we could not make them run whenever the procedure returns a DataSet (deleting, adding, updating or retrieveing single lines worked, but FOR SELECT did not work). So for those cases, we replaced the stored procedure call with a simple SQL Request, which was used to fill the target DataSet. For instance:

function EventsDB.f_c_get_events_2(p_module_idInteger): DataSet;
  const k_request=
        ' SELECT ItemID, Title, CreatedByUser, WhereWhen, '
      +       CreatedDateTitleExpireDateDescription'       + '   FROM Portal_Events'       + '   WHERE (ModuleID = ?)'; //  AND (ExpireDate > Current_timestamp)';
  var l_c_bdp_connectionBdpConnection;

    l_c_bdp_dataadapter:= BdpDataAdapter.Create(k_requestl_c_bdp_connection);
        BdpType.Int32BdpType.Int32, 4, System.Object(moduleId));

    l_c_dataset:= DataSet.Create;
    Result:= l_c_dataset;

  end// f_get_with_request_2

4 - The Delphi .Net Portal Configuration files

4.1 - Web.Config

This file contains the following information:


So the main information are

  • the Interbase connection string
  • the authentication mode (Forms or Windows)

4.2 - The Portal .XML Configuration File


Here is a view of the X_PORTAL_CONFIG.XML file (partial display):


Basically, this file tells us two things

  • how Modules are incorporated in the Views
  • the parameters of each Module
The link between the two is the "ModuleDefId"

The basic structure of this .XML can be represented by this free style BNF-like definition:

config.xmlheader <site_configuration ....
  header= <?XML ....
  site_configuration=  <SITECONFIGURATION global tabs module_definitions ....
    global= <GLOBAL ....
    tabs= { <TAB { module } ...> } .
      module= <MODULE  [ settings ] ....
        settings = <SETTINGS { setting } ....
          setting = <SETTING ....
    module_definitions= { <MODULEDEFINITION ...> } .


  • <GLOBAL > contains the main title and a global edition parameter
  • <TAB > describes one view: it corresponds to one of the banner "tab" (like the tabs of a notebook), and when this tab is selected, the modules specified within the <TAB> tag will be displayed
    • <MODULE > describes each module of the view: which module, at which position etc
      • <SETTINGS > for some modules, there are additional parameters, which are presented in this tag
  • <MODULEDEFINITION > describes each available modules (a module for discussions, one for links, one for .HTML text etc)

If we display the same information removing all .XML attribute keys, we get:

1  Home                      All Users;                1 
    1  QuickLinks                Admins; 8  LeftPane    0 1 false 
    2  Welcome to the Portal     Admins; 5  ContentPane 0 1 true 
    3  News and Features         Admins; 1  ContentPane 0 2 true 
    4  Upcoming Events           Admins; 4  ContentPane 0 3 true 
    5  This Weeks Special        Admins; 5  RightPane   0 1 false 
    6  Top Movers                Admins; 9  RightPane   0 2 false 

2 Employee Info All Users; 3     7 Spy Diary Admins; 5 LeftPane 0 1 false     8 HR Benefits Admins; 1 ContentPane 0 1 true     9 Employee Contact Inform. Admins; 2 ContentPane 0 2 true     10 New Employee Documentation Admins; 10 ContentPane 0 3 false

3 Product Info All Users; 5     11 Spy Diary Admins; 5 LeftPane 0 1 false     12 Competition: TradeCraft Admins; 1 ContentPane 0 1 true     13 Competition: Surveillance Admins; 1 ContentPane 0 2 true     14 Competition: Protection Admins; 1 ContentPane 0 3 true     15 Night Vision Goggles Admins; 6 RightPane 0 1 false     16 Competitors to Watch Admins; 8 RightPane 0 2 false

4 Discussions All Users; 7     17 Spy Diary Admins; 5 LeftPane 0 1 false     18 TradeCraft Techniques and Admins; 3 ContentPane 0 1 false     19 Recipes From the Field Admins; 3 ContentPane 0 2 false     20 GoodReads Admins; 10 ContentPane 0 3 false

5 About the Portal All Users; 9     21 Quick Links Admins; 8 LeftPane 0 1 false     22 About the Portal Starter Admins; 5 ContentPane 0 1 true     23 Portal Tabs Admins; 5 ContentPane 0 2 false     24 Portal Modules Admins 5 ContentPane 0 3 false     25 Managing the Portal Admins; 5 ContentPane 0 4 false     26 Managing Portal Layout Admins; 5 ContentPane 0 5 false     27 Managing User Security Admins; 5 ContentPane 0 6 false

6 Admin Admins; 11     28 Module Definitions Admins; 11 RightPane 0 1 false     29 Site Settings Admins; 14 ContentPane 0 1 false     30 Tabs Admins; 13 ContentPane 0 2 false     31 Security Roles Admins; 12 ContentPane 0 3 false     32 Manage Users Admins; 15 ContentPane 0 4 false

Announcements 5_module_components/a_c_announcements.ascx 1 Contacts 5_module_components/a_c_contacts.ascx 2 Discussion 5_module_components/a_c_discussion.ascx 3 Events 5_module_components/a_c_events.ascx 4 Html Document 5_module_components/a_c_html_module.ascx 5 Image 5_module_components/a_c_image_module.ascx 6 Links 5_module_components/a_c_links.ascx 7 QuickLinks 5_module_components/a_c_quick_links.ascx 8 XML/XSL 5_module_components/a_c_xml_module.ascx 9 Documents 5_module_components/a_c_document.ascx 10   Module Types (Admin) 6_admin_components/a_c_module_definitions.ascx 11 Roles (Admin) 6_admin_components/a_c_roles.ascx 12 Tabs (Admin) 6_admin_components/a_c_tabs.ascx 13 Site Settings (Admin) 6_admin_components/a_c_site_settings.ascx 14 Manage Users (Admin) 6_admin_components/a_c_users.ascx 15


On the code side, this .XML file is managed by the CLASSes contained in U_C_PORTAL_CONFIGURATION.PAS. This file was automatically generated using Babel_Code (a .CS to .PAS utility), from the original CONFIGURATION.CS file. This is a huge file (40 K for the .CS) and the Delphi counterpart has a 700 line INTERFACE.

This INTERFACE contains a main c_site_configuration CLASS modeling the whole .XML file, with many subclasses for each part of the .XML structure:

  • GlobalRow for <Global PortalId="0" ...>
  • TabRow for <Tab TabId="1" TabName="Home" ...>
  • ModuleRow for <Module ModuleId="1" ModuleTitle="QuickLinks" ...>
and with intermediate CLASSes for the structure (lists of tabs, lists of modulerow), which are called table_xxx, since they will be DataTable descendents. For the modules, for instance, we have:


The base c_site_configuration CLASS is essentially used by the U_C_CONFIGURATION's CLASSes (and also by U_C_SECURITY and U_MODULE_DEFINITIONS)


The configuration is then managed by several CLASSES contained in U_C_CONFIGURATION.PAS

The CLASSes of this unit also manage the configuration of the portal, but are not so tightly coupled with the .XML file. The information is retrieve from the .XML using the c_site_configuration CLASS, but it is massaged in several ways which are more easy to use in the different parts of the code.

The CLASSes found in this unit are:

  • c_module_info: allows to sort the Modules by ModuleOrder
  • c_module_item: encapsulation of the Module attributes. Mainly used by the administration pages
  • c_configuration: creates and modifies the basic c_site_configuration CLASS
  • c_tab_info= class: represents the Tab array
  • c_tab_strip_details: represent a single tab
  • c_tab_item: the main tab properties
  • c_portal_info: the main class used to get the configuration parameters

Note that
  • we replaced the original "_setting" denomination with "_info", since <SETTINGS> is used in the .XML file to specify some MODULE sub part

4.2.4 - Loading and Using the Configuration

The information contained in the .XML is loaded during the TGlobal.Application_BeginRequest (GLOBALS.PAS) event. This is one of the the very first event of each Asp.Net request, and it is used here to fetch the .XML and load it into an in-memory DataSet. A reference to this Dataset is included in the HTTP Context, which is available from nearly everywhere in Asp.Net.

Here is the DataSet creation:

var l_tab_indexl_tab_idinteger;

  if assigned(Request.Params['TabIndex'])
    then l_tab_index:= Int32.Parse(Request.Params['TabIndex'])
    else l_tab_index:= 0;
  if assigned(Request.Params['TabId'])
    then l_tab_id:= Int32.Parse(Request.Params['TabId'])
    else l_tab_id:= 1;

  l_c_portal_info:= c_portal_info.Create(l_tab_indexl_tab_id);



  • for the first request, there are no TabIndex, TabId parameters, so the default will be used (TabIndex=0, TabId=1, which is the main view)
  • whenever the USER clicks on a Tab, the parameters of this tab will be appended to the request, and the relevant configuration will be retrieved
In both cases, the configuration is now in the HTTP Context, which travels thru all the layers of the Asp.Net HTTP Pipeline, and will be retrieved using this context:
  • the 'portal_info_key' will retrieve the c_portal_info CLASS
  • the 'site_configuration_key' will retrieve the c_site_configuration CLASS (mainly in U_C_CONFIGURATION.PAS, and U_C_SECURITY.PAS)
For instance, to set the portal name in the banner (A_C_PORTAL_BANNER.PAS):

procedure TDesktopPortalBanner.Page_Load(senderSystem.Object ;
  var l_c_portal_infoc_portal_info;
    siteName.Text:= l_c_portal_info.PortalName;

5 - The User Files

We present here the elements that the normal user (not an administrator) will see. They are:
  • the Modules with their optional title
  • the Module Edit pages
  • the banner with the tabs

5.1 - The Modules

The Modules are Asp.Net components, which are created and placed on the View's Panes.

The module is made of a title and a content.

Here is the A_C_CONTACTS.ASCX in the Delphi IDE (the top third of the snapshot is the Delphi IDE part. Only the central white rectangle is the Module control):


and the same when loaded on the default page (the purple color comes from our CenterPane coloring):


In the present case, the module contains

  • a title custom component, which is an instance of the TDesktopModuleTitle Asp.Net component
  • the content of the Module (a DataGrid in this case)
This organization is not mandatory, but frequent. In fact, the Portal contains different Module types, to present how to handle different kind of informations: .HTML content, .XML content, .GIF images etc.

5.1.1 - The Module component ancestor

All Module components (user and administration) descend from a basic Asp.Net component which is used to handle the common attributes:

unit u_c_desktop_controls;
    uses ... ;

    type c_portal_module_control=

                 function GetModuleIdinteger;
                 function GetIsEditableboolean;
                 function GetSettingsHashtable;
                 property ModuleIdinteger read GetModuleId;
                 property PortalIdinteger read _PortalId write _PortalId;
                 property IsEditableboolean read GetIsEditable;
                 property ModuleConfiguration:
                     c_module_info read _moduleConfiguration
                     write _moduleConfiguration;
                 property SettingsHashtable read GetSettings;
               // ...
             end// c_portal_module_control


unit a_c_contacts;
    uses ...
        , u_c_desktop_controls;

    type TWebUserControlContacts=
                strict private
                  procedure InitializeComponent;
                strict private
                  procedure Page_Load(senderSystem.Object ;
                strict protected
                  procedure OnInit(eSystem.EventArgs); override;
              end// TWebUserControlContacts

5.1.2 - The Module Title

Most Module component use an instance of the a_c_desktop_module_title.ascx Module title Asp.Net component.

Here is a snapshot of this component displayed in the Delphi IDE:


This component contains

  • the ModuleTitle Label
  • the EditButton Hyperlink, which will be initialized to load the Module Edit .Aspx page

5.1.3 - Contacts.ascx

Here is the Contacts.Ascx template file


  • before the selected part is the .ASPx header
  • the selected part is the Module Title component (with its EditText title and EditUrl link for calling the Module edition a_edit_contacts.aspx page)
  • after the selected part is the DataGrid

On the action side, the a_c_contacts.TWebUserControlContacts.Page_Load event will fetch the events and fill the DataGrid :

procedure TWebUserControlContacts.Page_Load(senderSystem.Object ;
  var l_c_contact_dbContactsDB;
    l_c_contact_db:= ContactsDB.Create();
    l_c_dataset:= l_c_contact_db.GetContacts(ModuleId);

    if l_c_dataset.Tables.Count> 0
      then begin
          myDataGrid.DataSource:= l_c_contact_db.GetContacts(ModuleId);
  end// Page_Load

5.2 - The Portal Banner

Here is a snapshot of the a_c_desktop_portal_banner.ascx component in the Ide (with our debugging colors):

and the corresponding .ASCX template is:

<%@ Control Language="c#" AutoEventWireup="false"
<table class="HeadBg" cellspacing="0" width="100%" border="0"
    <tr valign="top">
      <td style="WIDTH: 68px; HEIGHT: 50px">
        <p align="center">
        <img style="WIDTH: 45px; HEIGHT: 40px" hspace="0"
              src="k_logo_png" align="middle" vspace="2" height="45">
          <font size="1">
            <a class="sitelink" href="k_">
        <td style="WIDTH: 336px; HEIGHT: 50px">
          & nbsp ;
          <asp:Label id="siteName" width="310px" height="32px" runat="server"
               cssclass="SiteTitle" font-size="20pt">
        <td style="WIDTH: 464px; HEIGHT: 50px">
          <p align="left">
          <asp:Label id="WelcomeMessage" width="342px" height="19"
              runat="server" forecolor="#eeeeee" backcolor="#C0FFFF">
          <a class="SiteLink"href="c:\programs\us\web\asp_net\
  <asp:DataList id="tabs" runat="server" cssclass="OtherTabsBg"
      itemstyle-borderwidth="1" selecteditemstyle-cssclass="TabBg"
      itemstyle-height="25" enableviewstate="false"
      repeatdirection="horizontal" bordercolor="White" backcolor="#C0C0FF">
    <SelectedItemStyle cssclass="TabBg">
      & nbsp ;
      <span class="SelectedTab">
        <%# ((u_c_configuration.c_tab_strip_details) Container.DataItem)
           .TabName %>
      & nbsp ;
    <ItemStyle height="25px" borderwidth="1px">
      & nbsp ;
      <a href='<%=u_c_portal_helpers.TGlobalHelpers
         .GetApplicationPath2(Request) %>/a_asp_portal.aspx
         ?tabindex=<%# Container.ItemIndex %>
         &tabid=<%# ((u_c_configuration.c_tab_strip_details)
         Container.DataItem).TabId %>
' class="OtherTabs">
         <%# ((u_c_configuration.c_tab_strip_details)
         Container.DataItem).TabName %>
      & nbsp ;


  • at the start is the header
  • then the .HTML Table for the logo, the home URL etc
  • at the end the DataList for the Tab array
And here is the Page_Load event which fills the Tab array:

procedure TDesktopPortalBanner.Page_Load(senderSystem.Object ;
  var l_c_portal_infoc_portal_info;
    l_c_portal_info:= c_portal_info(HttpContext.Current.Items['portal_info_key']);

    // -- set the site name
    siteName.Text:= l_c_portal_info.PortalName;

    // --- If user logged in, customize welcome message
    if Request.IsAuthenticated
          // --- if authentication mode is Cookie, provide a logoff link
          if (Context.User.Identity.AuthenticationType'Forms')
            then LogoffLink:= TGlobalHelpers.GetApplicationPath2(Request)+

          // --- initialize welcome, and the logoff link
          WelcomeMessage.Text:= (k_welcome_message.Replace('%1s'Context.
              User.Identity.Name))+ k_logoff_message.Replace('%1s',

    // -- set the active tab index
    TabIndex:= 1;
    if ShowTabs
      then TabIndex:= l_c_portal_info.m_c_active_tab_info.TabIndex
          // --- toggle visibility
          tabs.Visible:= FALSE;

    // -- Build list of tabs to be shown to user
    l_c_authorized_tab_array:= ArrayList.Create();

    // -- fill the array and set the selected index
    for l_tab_index:= 0 to l_c_portal_info.m_c_desktop_tab_array.Count- 1 do
      write_to_asp_net_log('select_tab ? 'l_tab_index.ToString);

      l_c_tab_strip_detail:= c_tab_strip_details(l_c_portal_info.m_c_desktop_tab_array[l_tab_index]);
      // -- add to the array if security is ok
      if c_portal_security.IsInRoles(l_c_tab_strip_detail.AuthorizedRoles)
        then l_c_authorized_tab_array.Add(l_c_tab_strip_detail);

      // -- set the selected tab
      if l_tab_indextabIndex
        then tabs.SelectedIndex:= l_tab_index;
    end// for l_tab_index

    // -- render the Page tab array with authorized tabs
    tabs.DataSource:= l_c_authorized_tab_array;
  end// Page_Load

5.3 - The Main page

The main page a_asp_portal.aspx looks like this:


and the initialization of the Modules in the Panes takes place during the OnInit event (the loading from cache has been removed in our display):

procedure t_asp_portal_form.OnInit(eEventArgs);
  var l_c_portal_infoc_portal_info;
    inherited OnInit(e);

    // -- Obtain c_portal_info from Current Context
    l_c_portal_info:= c_portal_info(HttpContext.Current.Items['portal_info_key']);

    // -- Ensure that the visiting user has access to the current page

    if not c_portal_security.IsInRoles(l_c_portal_info.m_c_active_tab_info.AuthorizedRoles)
      then Response.Redirect(k_access_denied_module_aspx);

    // -- Dynamically inject a signin login module into the top left-hand corner
    // -- of the home page if the client is not yet authenticated
    if not Request.IsAuthenticated and (l_c_portal_info.m_c_active_tab_info.TabIndex= 0)
          Page.FindControl('LeftPane').Visible:= true;

    // -- Dynamically Populate the Left, Center and Right pane sections of the portal page
    // -- with the modules defined in x_portal_cfg
    if l_c_portal_info.m_c_active_tab_info.Modules.Count> 0
        // -- Loop through each entry in the configuration system for this tab
        for l_module_index:= 0 to l_c_portal_info.m_c_active_tab_info.Modules.Count- 1 do
          // -- in which parent pane should this module be positioned ?
          l_c_parent_control:= Page.FindControl(l_c_module_info.PaneName);

          // -- create the user control instance and dynamically
          // -- inject it into the page.

          l_c_portal_module_control.PortalId:= l_c_portal_info.PortalId;
          l_c_portal_module_control.ModuleConfiguration:= l_c_module_info;

          // -- add the module to its parent

          // -- Dynamically inject separator break between portal modules
          l_c_parent_control.Visible:= true;
        end// for l_module_index
  end// OnInit

5.4 - The Global.Pas File

The Global.BeginRequest, which has already been partially presented above, and here is a more complete version (the language initialization removed):

procedure TGlobal.Application_BeginRequest(senderSystem.Object ;
  var l_tab_indexl_tab_idinteger;
    // -- default values
    l_tab_index:= 0;
    l_tab_id:= 1;

    // -- Get l_tab_index from querystring
    if (assigned(Request.Params['TabIndex']))
      then l_tab_index:= Int32.Parse(Request.Params['TabIndex']);
    // -- Get l_tab_id from querystring
    if (assigned(Request.Params['TabId']))
      then l_tab_id:= Int32.Parse(Request.Params['TabId']);

    l_c_portal_info:= c_portal_info.Create(l_tab_indexl_tab_id);
    // -- Retrieve and add the configuration DataSet to the current Context
  end// Application_BeginRequest

5.5 - The basic cycle

5.5.1 - The User calls the first time

The basic scenario is the following:
   the Web Server waits for incoming requests:


   the User calls the default a_asp_portal.aspx file using his Browser


   the Asp.Net Server gets the request and then
  • calls the Globals.BeginRequest event to load the configuration and include it in the Http.Context
  • the a_asp_portal.aspx.Page_Load event is called. Since there is no ModuleId parameter, the default value of 1, corresponding to the first tab is used. The configuration for this View is grabbed from the Context, which tells which Modules should be loaded
  • each Module is built in turn, with its title and content filled
The corresponding page is sent back the user


Here is what the User will get:


In an UML Sequence diagram, this would look like this:


Granted, this is not a bona-fide UML sequence diagram. Usually:

  • they contain Objets at the top of the diagram (and not file names)
  • Grady Booch included neither any object-container, like we used for modules, nor any multiplicity (modules again)
  • when an .ASPX contains an Asp.Net custom control, there is no visible method call to instanciate the component class (at the developer level). This is performed inside Asp.Net
  • and, or course, there are no fancy colors !
We could have presented the standard events called upon each Asp.Net Request: BeginRequest(), AuthenticateRequest(), AuthorizeRequest(), ... all the way up to PreSendRequest(). But this would have been too detailed, given the number of components involved here.
So we have presented this diagram instead, hoping that it somehow still conveys the processing involved at the Web Server level.

5.5.2 - The User selects another Tab

When the User has received his .HTML page, he can click on a tab or on any hyperlink.

Selecting any Tab, this will trigger the "OnClick" event of this item. This handling is located in the a_c_desktop_portal_banner.ascx template. The text of the .ASCX has been presented above, but the real processing has been drowned in the formating flood. Here is a more focused version:



  • the selected tab title is drawn using:


  • the non-selected tab titles are drawn using:


  • these lines build the




These snippets display the TEMPLATE code, which is located on the Server. When the Server receives a request, it uses the template and the .PAS code to build the true .HTML page that is sent over to the User. In our case, after receiving the default page, we saved it on disc, and here is a partial listing of this default view:


So when the User clicks on a tab, it is the hilighted link that is clicked, and this link contains exactly the .HTML request for the page with the View parameters.

The scenario looks like this
   the User clicks a Tab, which sends the request to the Server


   the Server builds the page and returns the .HTML to the User:


And here is what the user will see after clicking the "Product Info" tab:


6 - The Administration Files

Those Modules are used to let users with Administrator rights modify the x_portal_cfg.xml file, which in fact reorganizes the presentation of the Views.

We will not present those Modules in this paper

7 - What is missing - Lessons learned

7.1 - What is missing in this downloadable version

You can download the .ZIP of the project. In those files, we removed the following parts
  • all Mobile files were removed. We do not use a mobile so we could not test those parts. The mobile parameters are however still present in the configuration files and CLASSes
  • we commented out the security part. When we started to use the Portal, we could not access to anything, and we had to use a Data Explorer to understand the password and registering system. In addition, having to fight with passwords during all trials tests was a pain in the neck. So we commented out the security checks
  • we removed the security (logging, roles etc) and the Asp.Net caching
In addition, our purpose being the presentation of the overall architecture, we did not test all features. For instance:
  • we did not check the User editing
  • we did not check the administration Modules
In the production version of the portal, the security and caching are reinstalled.

7.2 - The Renaming and Prefixing

In order to be sure that we understood the interactions between all the parts, we performed, as ususal, a massive renaming of files and variables.

Lets take an example: what is Text ? Text is:

  • the name of a file Type (the ascii buffered FILE)
  • the name of a tWinComponent PROPERTY (the Win32 WindowString equivalent), which becomes Caption or Text, and is available even in tForms, even if it is not surfaced in the Object Inspector
But since Text is a predefined word (and not a KEYword), you can name any identifier of your choice Text: a CONST, a TYPE, a VAR, and also parameters, procedures, functions, methods etc. But because of the possible conflicts with the previous significations, WE will NEVER use "text" as the name of one of OUR identifiers.

Our usual solution is to use a prefixing scheme, much similar to Charles SYMONY's Hungarian notation (a "T" prefix for types, a "wm_" prefix for Windows Messages etc), which is the Alsacian Notation, which prefixes types with T_, constants with K_, globals with G_, locals with L_, classes with C_ etc.

In this case:

  • we renamed all files with the U_ (units) or A_ (ASPX or .ASCX) prefix. The ONLY files we could not change were GLOBALS and WEB.CONFIG, because this seems to be hard coded in the ASP.NET system.
  • many (not all) of the CLASSes were renamed C_xxx, and many (not all) of the local variables were prefixed with L_xxx
  • we renamed ALL litteral strings in the .ASPX and .ASCX files with the K_ prefix
Renaming of files and identifiers has the additional huge benefit of narrowing down the results of any "find in file" request (l_c_announcement_db is more discriminating than announcements) as well as allowing further automated renaming for internationalization, for instance (renaming everywhere k_Name is easier and less error prone than renaming Name).

7.3 - What was added

We did add
  • we added file logging capabilities, in order to find bugs and trace the different steps of the execution.
  • we added colors and separators to the main template, in order to better visualize the View contents of the pages
The logging and coloring have been removed in the production version.

7.4 - Our biggest hurdles

We were mainly slown down by the following points
  • the switch from FireBird / Sql Server to Interbase. The whole scripts had to be recovered and adapted (they are in the .ZIPs)
  • with the scripts, the loading of the .HTML blobs also caused some problems. We first forgot to remove the quotes. Then the reengineered versions had some .HTML encoding which had to be undone. And an obscure bug in our blob loader removed the first character of the blob, which turned out to be catastrophic when the first character is the "<" start of an .HMTL tag (when erroneous .HMTL text is injected in the page, the whole .HTML Table structure can be thrown off balance)
  • the use of the BDP access layer. Some parts would not work (Stored Procedures with SELECT FOR constructs)
  • the use of the Interbase Delphi version also limits the Portal navigation, since the user count can very quickly get beyond the 4 user limit. So there should be better check on closing the Database after retrieving the rows
  • Cassini also had some synchronization errors: when the Delphi compilation is complex (many modifications), sometimes Cassini is launched after the Browser request. They are some advices on Tamaracka, but we did not implement them in this debug version. In addition, closing the .HTML and Cassini sometimes blocked the system. So we systematically display the Task Manager and place its icon in the task bar in order to close those files only when all Asp.Net threading activity has stopped.

7.5 - Basic advice

If we had some advice to developers wanting to change the code
  • use an Asp.Net logging mechanism. This really was the main debugging tool, which helped us to quickly locate all database problems.

    Here is the (partial) log of the building of the default view:

    06/01/2007 09:22  > Global.begin_request 
    06/01/2007 09:22    raw_url /p_delphi_asp_net_portal/a_asp_portal.aspx 
    06/01/2007 09:22    tab_ix 0 tab_id 1 
    06/01/2007 09:22    create_portal_settings 
    06/01/2007 09:22    > c_portal_info.Create. tabId=1 
    06/01/2007 09:22    < c_portal_info.Create 
    06/01/2007 09:22    add_to_HttpContext 
    06/01/2007 09:22  < Global.begin_request 
    06/01/2007 09:22 
    06/01/2007 09:22  > TGlobal.Application_AuthenticateRequest 
    06/01/2007 09:22  < TGlobal.Application_AuthenticateRequest 
    06/01/2007 09:22 
    06/01/2007 09:22  TGlobal.SessionStart 
    06/01/2007 09:22 
    06/01/2007 09:22  > t_asp_portal_form.OnInit 
    06/01/2007 09:22    FilePath /p_delphi_asp_net_portal/a_asp_portal.aspx 

    06/01/2007 09:22 ./3_elements\31_user_components\a_c_signin.ascx 06/01/2007 09:22 tab Home has 6 modules 06/01/2007 09:22 load_module_index 0 06/01/2007 09:22 This Weeks Special 3_elements/31_user_components/a_c_html_module.ascx 06/01/2007 09:22 panel=RightPane display_order 1 06/01/2007 09:22 load_module_index 1 06/01/2007 09:22 Welcome to the Portal Starter Kit 3_elements/31_user_components/a_c_html_module.ascx 06/01/2007 09:22 panel=ContentPane display_order 1 ... 06/01/2007 09:22 < t_asp_portal_form.OnInit 06/01/2007 09:22 06/01/2007 09:22 > TDesktopPortalBanner.Page_Load 06/01/2007 09:22 select_tab ? 0 06/01/2007 09:22 select_tab ? 1 06/01/2007 09:22 select_tab ? 2 06/01/2007 09:22 select_tab ? 3 06/01/2007 09:22 select_tab ? 4 06/01/2007 09:22 select_tab ? 5 06/01/2007 09:22 < TDesktopPortalBanner.Page_Load 06/01/2007 09:22 06/01/2007 09:22 > TWebUserControlQuickLinks.Page_Load 06/01/2007 09:22 c_portal_module_control.GetIsEditable 06/01/2007 09:22 c_portal_module_control.GetModuleId 1 06/01/2007 09:22 > LinkDB.GetLinks 1 06/01/2007 09:22 portal_open_bdp_con 0 LinkDB.GetLinks 06/01/2007 09:22 open_bdp_con 0 LinkDB.GetLinks 06/01/2007 09:22 < LinkDB.GetLinks 06/01/2007 09:22 c_portal_module_control.GetModuleId 1 06/01/2007 09:22 ?_allow_edit 06/01/2007 09:22 < TWebUserControlQuickLinks.Page_Load ...

  • we also added coloring to the a_asp_portal.aspx page, and to the portal banner to better visualize the different parts
  • finally, we had to use or build many Delphi tools, some of which will be described below.

7.6 - Asp.Net development Tools

Here are some of the tools we used:
  • first, in order first to convert the Database to Interbase, we used many Database tools (script extractor, script executer, blob loader, UDF tester, SP tester)
  • the Asp.Net logging unit was already mentioned
  • ASCII "find in files" (searching in SEVERAL EXTENSION files at the same time), as well as filtering and replacement tools (listing all tag attributes, finding all litteral strings etc)
  • different syntactic color viewers and pretty printers for the ASPX, ASCX, XML and CONFIG files
Like for any important project (an accounting system, a text editor, a vector graphic tool), much of the value is derived from the additional applications which help develop the project and extend its functionality.

Many of our tools have been published with source (the log, the .HTML color display etc). Just look at the site map or use google to locate them

8 - Download the Sources

Here are the source code files:

The .ZIP file(s) contain:
  • the main program (.BDSPROJ, .DPR, .RES, .CSS, .ASAX), the main form (.PAS, .ASPX), and any other auxiliary form and files (.XML)
  • any .TXT for parameters, samples, test data
  • all units (.PAS) for units and templates (.ASCX, .ASPX)
  • the SQL scripts and binaries
Those .ZIP
  • are self-contained: you will not need any other product
  • will not modify your PC in any way (no registry changes, no hidden file, no .BAT etc).
To use the .ZIP:
  • create C:\PROGRAMS\HELPERS\COLIBRI_DOT_NET_HELPERS\ folder and unzip the file
  • create the C:\PROGRAMS\US\WEB\ASP_NET\DELPHI_ASP_PORTAL\_DATA\ folder and unzip the
  • unzip the, and save the d_string_udf.dll in your Interbase UDF folder (in our case: C:\Program Files\Borland\InterBase\UDF\)

    If you decide to build the database yourself:

    • create the database
    • unzip the d_string_udf.dll
    • unzip the Sql scripts and generate the database
  • create the C:\PROGRAMS\US\WEB\ASP_NET\DELPHI_ASP_PORTAL\PROG and unzip the
  • using Delphi, compile and execute
  • make sure CASSINI is ready, and specified as the Web Server (Delphi 2006, Options, Asp), or otherwise set up your favorite Asp.Net Web Server
For using Interbase 7.5, unzip AND change the connection string and database name in WebConfig.

To remove the .ZIP simply delete the folder.

As usual:

  • please tell us at if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will be helpful for other readers
  • we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to
  • or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
    Name :
    E-mail :
    Comments * :

  • and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate: the more traffic and Google references we get, the more articles we will write.

9 - References

  • The Asp.Net Starter Kit The Asp.Net Starter Kit : the starter kit in action

    In January 2007, this URL was no longer valid, and redirected the user to the Dot Net Nuke Site. However the URL still offers Asp.Net portals of many different makes and sorts.

    We recently found out that the Asp.Net version 1 can still be found in the Asp.Net 1.1 archive page, but this could change

  • The Microsoft Dowload: the Microsoft "CSSDK" msdn entry contains the "ASP.NET Portal (CSSDK) Installer v1.0.msi" "Starter Kit" downloadable file (1.751 Meg)

    In January 2007, this URL also does not seem to be still valid, but googling around, you might still find the original Starter Kit

  • Pascal Chapuis uploaded his Delphi CSSDK Delphi translation of the Starter Kit on Code Central. It can be freely downloaded from there. This excellent piece of code was the starting point of the Starter Kit version we presented here

  • Hungarian Notation: the Hungarian Notation explained

  • on the tool side:
  • finally "a word from our sponsor": the Pascal Institute organises each month training courses about Delphi programming. Of concern to an Asp Portal developer: I am the only teacher, so, if you liked this presentation, you should love the courses

    In addition we offer support or full development in the area of Delphi, Database management or Web development

    To contact us, just send an e-mail to

10 - The author

Felix John COLIBRI works at the Pascal Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi Xe_n migrations, refactoring), Delphi Consulting and Delph training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP  /  UML, Design Patterns, Unit Testing training sessions.
Created: jan-07. Last updated: jul-15 - 98 articles, 131 .ZIP sources, 1012 figures
Copyright © Felix J. Colibri 2004 - 2015. All rigths reserved
Back:    Home  Papers  Training  Delphi developments  Links  Download
the Pascal Institute


+ Home
  + articles_with_sources
    + database
    + web_internet_sockets
      – tcp_ip_sniffer
      – socket_programming
      – socket_architecture
      – simple_web_server
      – simple_cgi_web_server
      – cgi_database_browser
      – whois
      – web_downloader
      – web_spider
      – rss_reader
      – news_message_tree
      – indy_news_reader
      – delphi_web_designer
      – intraweb_architecture
      – ajax_tutorial
      – bayesian_spam_filter
      + asp_net
        – delph_asp_net_portal
        – cassini_spy
        – asp_net_log_file
        – viewstate_viewer
        – master_pages
        – asp_net_20_databases
        – asp_net_20_security
    + oop_components
    + uml_design_patterns
    + debug_and_test
    + graphic
    + controls
    + colibri_utilities
    + colibri_helpers
    + delphi
    + firemonkey
    + compilers
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Site Map
– search :

RSS feed