Introduction
This document is intended to be read as a general overview of Internet/enterprise technology to get newbies to this domain up to speed. The contents of this document is arranged to provide:
The document was originally researched and written by myself in 1997. However, it is still very relevant today (c. 2005). I am publishing it here freely for you so that beginner ASP.NET developers today can appreciate and understand how ASP.NET and database connection technology came about and how it has evolved over the years.
I hope you find this enough information for you to get you started developing enterprise applications for the Internet...
Comments, suggestions welcome to: kevleyski@hotmail.com.
Kevin Staunton-Lambert BSCS
Quick overview of the Internet and TCP/IP
The �Internet� was the name given to the project and prototype system which was originally developed by the Advanced Research Projects Agency (ARPA) to investigate ways to solve the problem of getting incompatible computer networks to communicate with one another.
Through this project, two fundamental software standards were developed:
- Transmission Control Protocol (TCP) which makes sure that all data passed through the network arrives as it was intended.
- Internet Protocol (IP) which specifies the �language� which must be spoken and understood by any computer system to communicate with another over the Internet.
These software standards are generally referred to as �TCP/IP� (Tea Sea Pea Eye Pea). However, a more precise title for it is �TCP/IP protocol suite� because this software also includes other protocols such as the User Datagram Protocol (UDP) which is used for short packets of data such as live video and audio which do not require error checking.
Any machine connected to the Internet has a unique Internet (IP) address. The IP address is a four byte code (32-bit code which has the potential to support over 4 billion machines) which is assigned so that all machines belonging to a similar network have the same prefix. (This is similar to telephone numbers being grouped into locations, however location is irrelevant when assigning IP addresses).
There is currently an improved IP addressing system being developed known as IPng (IP Next Generation) which will use eight bytes (128-bit) to support 3x1038 machines, sufficient enough to potentially make it possible to control any light-switch in any house on the planet without any worries of running out of addresses. (I expect that we will see some interesting computer viruses should this ever be implemented!)
Humans are generally not very good at remembering IP addresses, so they are often assigned a name commonly known as Host Name, Universal Resource Locator (URL), or Universal Resource Identifier (URI). An example of such a name is http://www.codeproject.com. URLs along with the IP address are held as an entry on a Domain Name System (DNS) which is essentially a series of computers known as 'domain servers' on the Internet supporting this ever increasing database.
The World Wide Web (WWW)
The World Wide Web (WWW) was originally developed by Tim Berners-Lee and other scientists at CERN laboratories (in Geneva just on the French border) to allow particle physicists to share information around the world.
Today �The Web� as it is generally called, is used by millions of people around the world to pass and organize hypermedia (text/graphics/sound etc.) over the Internet. Estimating the size of the web is a near impossible task. On 25th May 1996, it was estimated by 'Internet Solutions' that there were 59,628,024 people accessing 304,177 sites; today in 1998, these figures may have near doubled.
The Web�s success has been achieved by creating hypermedia document standards:
- The HyperText Transport Protocol (HTTP) which is used by all web �Browsers� to communicate with web �Servers� (sometimes this is also referred as the HyperText Transmission Protocol).
- HyperText Mark-up Language (HTML) which is the �language� which must be used by web page authors to format the hypermedia documents. (It is often the specification of this language that has sparked major controversy between some software producers.)
Mark-up Languages
HTML (Hyper Text Markup Language)
HTML is the default scripting language supported by the HyperText Transfer Protocol (HTTP) for formatting web documents (formatting tags) and hyper-linking (anchor tags) to other web documents and scripts held on a web server.
The syntax for HTML, which was adapted from SGML (Simple Generic Mark-up Language or Standard Generalized Markup Language ISO 8878:1986), is very easy to learn and is ideal for use on the web because of its low size. (Compare the size of a formatted HTML document with a similarly formatted document written using a Word processor). HTML documents are generally held in ASCII (American Standard Code for Information Interchange) format which is a standard between most software/hardware platforms. International coding characters (Unicode) can also be added to documents by using their HTML abbreviation after an ampersand (&) character, and then followed by a semi-colon (;). (For example, the symbol for the ampersand character is marked up as &.)
Although there is plenty of material covering HTML, we need to look at some basic HTML structures because we will keep coming across them in the following sections.
HTML document structure and Tags
HTML documents always have a basic structure made up of a header (containing the document title, and other meta-data such as the author's name and date) and a body. Documents are essentially plain text with formatting tags which are very similar to the principles used by early word processors, such as Word Perfect for DOS. HTML Tags are defined between less than (<) and greater than (>) symbols and always come in pairs *, i.e., a section start tag (e.g., <large> to make text large) and a section closing tag (e.g., </large> to make it normal again).
* Some browsers, such as Microsoft Internet Explorer allow lazy HTML as well as strict HTML. This allows you to miss out some tags which may seem obvious (such as the tag used to end a row in a table (</tr>)). However, it is very important to keep to the official strict syntax defined by the WWW Consortium (W3C) so that we can maintain software independence throughout the web.
The following HTML script demonstrates some of the formatting tags.
Notice the use of indentation to identify the affected section between a start and closing tag; this is valid HTML because tabs, and spaces larger than one character in length, are ignored. (To force double spaces, you need to use the HTML abbreviation
).
Section |
HTML Script |
Details |
Comments |
<!---->
|
Any text within the tags <!-- and --> is specified as not being part of the HTML content.
Do not use hyphens (-) or the 'greater than' symbol (>) in a comments section because the HTML browser may confuse this as the end of the comment. |
Document Versioning Information |
<!doctype HTML PUBLIC
"-//W3C//DTD HTML 3.2//EN">
<html>
|
<!doctype> specifies which version of HTML should be used to interpret this document. Here, we have specified version 3.2 as defined by the WWW Consortium (W3C).
<html> Specifies that the start of the HTML document.
|
Header
Document Header |
<head>
<title>
Document Title
</title> |
<head> Denotes the start of the document header section.
<title> specifies the documents reference name (use for indexing search engines, and bookmarks/ favorites).
|
Header
Header Meta-data |
<meta HTTP-EQUIV="Content-Type"
CONTENT="text/html;
charset=Windows-1251">
<meta HTTP-EQUIV="Keywords"
CONTENT="HTML,Tables,Forms">
</head> |
<meta> Meta Data (hidden data) describes additional information such as the character set to use to display this document.
Other metadata includes document description, author, keywords, automatic document refresh/referral, published/expiry dates, etc �
</head> marks the end of document header section.
|
Body |
<body bgcolor=#ffffff
text=#000000
link=#0000ff vlink=#909090> |
<body> Start of document body section. The background/ text/ hyperlinks and visited hyperlink colors are defined here by using hexadecimal RGB (#rrggbb) notation (#ffffff specifies a white background). |
Tables
Table Header
|
<table>
<tr>
<th>
Tag
</th>
<th align=left>
Example
</th>
</tr>
|
<table> Specifies that the following lines are to be place in a table.
<tr> Add a row to the table
<th> Add a column called 'Tag' (table header)
Add another column called 'Example' which is to be align ed to the left of the column rather than the default middle alignment.
</tr> Specify the end of a row. * As mentioned above some browsers that support tables do not require this tag, however I have included it for completeness.
|
Tables
Table Rows
|
<TABLE cellSpacing=0 border=1>
<TBODY>
<TR>
<TD>1</TD>
<TD>2</TD>
<TD>3</TD>
</TR>
<TR>
<TD>4</TD>
<TD>5</TD>
<TD>6</TD>
</TR>
<TR>
<TD>7</TD>
<TD>8</TD>
<TD>9</TD>
</TR>
</TBODY>
</TABLE> |
Add another row
<td> Create a cell in the table (In the first column 'Tag'), right align the following text in this cell.
The <h1> tag specifies that the following code should be formatted as defined by the HTML browser as pre-set header 1.
</h1> Marks the end of the header line
</table> Denotes the end of the table
|
Breaks |
<hr size=3>
<br> |
<hr> places a horizontal rule (line break)
<br> denotes a standard carriage return line break
|
Lists
- Numbered
- ordered
- list
|
<ul>
<li>Bulleted</li>
<li>un-ordered</li>
<li>list</li>
</ul>
|
These lines demonstrate an HTML un-ordered bulleted list. <ul>
There is no limit to the number of list items (<li> item </li> )
An automatically numbered list (ordered list) is obtained by using the list tags <ol> and </ol> rather than <ul> and </ul> .
(valign=top prevents the text 'Lists: ' from appearing halfway down the cell.) |
Typefaces
Arial
Roman
Courier |
<font color=green
face='courier' size=6>
Green
</font> |
These lines demonstrate user-defined typefaces using the <font> tag. (N.B. Use American spellings.)
The example at the base of this page also demonstrates the use of RGB hexadecimal colour coding (#RRGGBB) for specifying non-standard colours. |
Hyperlinks
bookmark
document
e-mail |
<a href='http://...#ODBC'></a>
<a
href='mailto:
kevleyski@hotmail.com'>
</a> |
<a> The first hyperlink (anchor) moves the current document to the specified URL (Uniform Resource Locator), the second generates a new e-mail addressed to myself.
The href section of both these anchors can be split into several parts for understanding.
- Protocol, e.g. http: (HyperText Transfer), mailto: (e-mail), ftp: (File Transfer), telnet: (Remote Login), file: (Local file)
- Domain Name (for mailto: name@domain)
- (First example) The anchor name in document (Bookmark) to jump to specified after the hash (#) symbol.
|
Java Applets
<applet id=Object1 codeBase= /examples/ JavaExample/ height=110 width=110 align=baseline code= JavaText.class name=" Object1"> <PARAM VALUE="2910" NAME="_cx"> <PARAM VALUE="2910" NAME="_cy"> Example of a simple Java applet that animates text</applet> |
<applet code="JavaText.class"
codebase="/examples/JavaText"
width="110" height="110">
<param name="text" value="Java">
</applet>
|
<applet> Include Java applet (.class file) in a web page.
<param> Set a parameter to send to the Java applet.
|
ActiveX Controls
<object id=Birthday height=110 width=110 align=baseline border=0 classid=" clsid:8E27C92B-1264-101C-8A2F-040224009C02"> <PARAM VALUE="28" NAME="Day"> <PARAM VALUE="05" NAME="Month"> </object> |
<object id="Birthday"
classid="clsid:8E27C92B-
1264-101C-8A2F-040224009C02"
width="110" height="110")
<param name="Day" value="28">
<param name="Month" value="5">
</object> |
<object> Include ActiveX control.
(This example uses the Microsoft Calendar ActiveX Control which has the international class id entification 8E27C92B-1264-101C- 8A2F-040224009C02)
Similar to Java Applets the parameters are specific to the control, here we define the date 28th May. |
Cascading Style Sheets (CSS)
Style 1
Style 2
Re-styled header (h1) |
<style>
.style1
{font: bold 28pt Times,serif;}
.style2
{font: italic 14pt Times,serif;}
h1
{font: 8pt Arial; color: blue}
</style>
<p class='style1'>Style 1</p>
<p class='style2'>Style 2</p>
<h1>Re-styled header (h1)</h1> |
<style> Styles are used in the same way as we might use styles with a word-processor to set the attributes of various types of text, such as a title.
User defined styles are set by choosing a name and preceding it with a full stop (.), pre-set styles, (such as <h1> / <p> / <cite> / <strong> / <small> ) can be re-defined by using the given name of the style. |
Document footer |
</body>
</html> |
Specify the end of the body section and the HTML document |
XML (eXtensible Mark-up Language)
So far we have looked at the syntax (grammar) of HTML. However, like all languages, we should also consider the semantics (meaning) of the information we are portraying.
For example, if text has been marked up in bold, it could be because it has more importance, or it might be because the author wants it to look like that. Similarly, we might use a colored typeface to denote a title, or again it might be personal preference to make the page appear more attractive.
Essentially, the point of this argument is that if text is desired to be displayed as bold because it is of more importance, then it should be marked up with the logical tag <strong>
rather than the physical <b>
. Likewise, if a sentence is a title header, then we should consider using a logical tag such as <h1>
rather than forcing a style using physical tags such as <font size=6>
.
As mentioned above, we can override existing, and define our own logical tags using cascading style sheets. However, it can look clumsy, e.g., having to repeat tags such as <p class='Style1'>
, and for this reason, is often harder for the reader to gain meaning from than not use it at all.
To tackle this problem, XML, unlike HTML, does not use preset tags so it is entirely up to the author how a page is formatted. The use of nested metadata tags, i.e. information about information, is also introduced. For example, say that we have a piece of information related to this paragraph.
We might physically mark it up using HTML as follows:
<b>Classification number</b> 1:02:01:13:00 <br>
<b>Thesaurus entry</b> XML <br>
<b>Article</b> Extensible Markup Language <br>
<b>URL</b> /dissertation/week2/HTML.html <br>
<b>Bookmark</b> XML
In XML, we would instead consider the semantics of the information, such as:
<Article>
<Classification>
<Level1> 1 </Level1>
<Level2> 02 </Level2>
<Level3> 01 </Level3>
<Level4> 13 </Level4>
<Level5> 00 </Level5>
</Classification>
<Thesaurus> XML </Thesaurus>
<Article> Extensible Markup Language </Article>
<URL> /dissertation/week2/HTML.html </URL>
<Bookmark> XML </Bookmark>
</Article>
The author is then left to their own devises in writing an XSL (Extended Style Language) style sheet (see www.w3.org/Style/) which will correctly format any section marked up as 'Article' on a web page back to the original desired HTML format.
Web Forms
Forms are used to pass user input to a web server from an HTML document. You should already be quite familiar with the behavior of the Form controls as they are used extensively in Graphical User Interfaces (GUIs) such as MS Windows, MacOS and X-Windows. (Microsoft ActiveX Controls can also be incorporated into a form to provide additional input types (e.g., Date format) and user input styles.)
To better understand how forms are actually processed by the web server, set the form action to call the ISAPI example described later. (Try method= "POST"
as well as the default "GET
" to see the difference in use of the CGI querystring.)
Section |
HTML Script |
Details |
Form handler specification |
<form
action="/examples/ CGIExample/CGIExample.exe"> |
<form> . This tag specifies that the parameters from the following input controls (up to </form> ) should be passed as parameters (action ed) to the program ISAPIExample.dll. (Covered later.) |
Text Input Box
<input id=" Text1" value= "Text Box" name= textbox>
|
<input type="text" size="20"
name="textbox" value="Text Box"> |
text . This text input box is used to generate the parameter name d textbox . The initial value of the parameter is set to Text Box and the size of the input box is 20 characters wide. |
Password Input Box
<input id=Password1 type= password value= Password name= password>
|
<input type="password"
size="20" name="password" value="Password"> |
password . The password box is implemented in the same way as the text box. Essentially the difference is that any characters entered into the text box are displayed as asterisks (*). |
Multi-line (scrolling) Input Box
<textarea id=Textarea1 name=areabox rows=3> Example of a multiple line input box (Text Area) </textarea>
|
<textarea name="areabox" rows="3" cols="20">
Example of a multiple line input box (Text Area)
</textarea> |
<textarea> . This multiple line input box (or Text Area), generates the parameter named areabox.
The initial value is set by the text between the start tag and end tag, the size is represented in row s (3) and col umns (20). |
Check boxes
<input id=" Checkbox1" type= checkbox value=chosen name=check1> 1 <input id=Checkbox2 type= checkbox value= chosen name=check2> 2 <input id= Checkbox3 type= checkbox CHECKED value= chosen name=check3> 3
|
<input type="checkbox"
name="check1" value="chosen">1
<input type="checkbox"
name="check2" value="chosen">2
<input type="checkbox"
checked name="check3" value="chosen">3 |
checkbox . These three checkboxes define 3 separate parameters name d check1 , check2 and check3 .
The value chosen is the value of the parameter if it is checked; if the checkbox is not checked, then the entire parameter is simply not passed to the web server.
Notice that the third checkbox is specified to be initially checked . |
Options (Radio)
<input id=" Radio1" type=radio CHECKED value=1 name= optiongroup> 1 <input id=" Radio2" type=radio value=2 name= optiongroup> 2 <input id="Radio3" type=radio value=3 name= optiongroup> 3
|
<input type="radio"
checked name="optiongroup" value="1">1
<input type="radio"
name="optiongroup" value="2">2
<input type="radio"
name="optiongroup" value="3">3 |
radio . Unlike checkboxes this radio control defines only 1 parameters name d optiongroup . The value of this parameter depends on the option selected, i.e. 1, 2 or 3.
Notice that option 1 is the option that is initially chosen (checked ). |
Options (List)
<select id=Select1 size=1 name =combobox> <option value=1> Option 1</option> <option value=2> Option 2</option> <option value=3 selected> Option 3</option> <option value=4> Option 4</option> </select>
|
<select name="combobox" size="1">
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option selected value="3">
Option 3</option>
<option value="4">Option 4</option>
</select> |
<select> This control defines a single parameter name d combobox . The value depends on the pre-set option selected from the drop down list. In this example if the user selects 'Option 2' from the list the parameters value is 2.
(N.B. If we did not specify a value within the <option> tag, the value would have been the string 'Option 2')
The size parameter of the <select> tag determines whether or not the options are presented as a combo box or a list box, i.e. 1 = Combo Box and >1 = List box of that size.
The initial option selected here has been set to 'Option 3' |
Multiple Options (List)
<select id=Select2 multiple size=4 name= listbox> <option value=1 selected> Option 1</option> <option value=2> Option 2</option> <option value=3> Option 3</option> <option value=4 selected> Option 4</option> </select>
|
<select name="listbox" multiple size="4">
<option selected value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
<option selected value="4">Option 4</option>
</select> |
multiple When a <select> control is specified as multiple, several parameters all name d listbox are generated depending on the options selected.
(N.B. A drop down combo box (i.e. size=1) which is set as a multiple selection automatically becomes a list box)
To make multiple selections click the mouse while holding the ctrl key. (Or hold the shift key to select a group from the previous selected option.) |
Buttons
<input id=" Submit1" type=submit value="Submit 1" name=pressed>
<input id=" Submit2" type=submit value="Submit 2" name=pressed>
<input id=" Image1" type=image height=40 width=125 src= "button.gif" align=" bottom" value="Submit 3" border=0 name= pressed>
<input id=Reset1 type=reset value=Reset name=Reset1>
|
<input type="submit"
name="pressed" value="Submit 1">
<input type="submit"
name="pressed" value="Submit 2">
<input type="reset">
<input type="image" name="pressed"
src="/dissertation/week3/button.gif"> |
submit This button type actions the form, i.e. it passes the parameters to the program defined in the <form> tag.
This example demonstrates how to generate an additional parameter (name d pressed ) to distinguish between which button was used to submit the form. However, if we only require one submit button, we can use the condensed form of this statement, <input type= "submit">; this also gives the added benefit of enabling the form to be submitted when the user presses the return key.
image The image input type submits a form (as above) however the button face contains an image source rather than a value. When the user clicks on the image two parameters are passed, pressed.x and pressed.y and their respective 'click position' values rather than pre-sets value s as used with the submit buttons above.
reset This button type simply un-does any changes the user may have made to the form by resetting the form controls to their respective initial states.
|
Hidden |
<input type="hidden" name="somedata"
value="UsefulDataYourAppProcesses"> |
hidden . Use this to send hidden data, that is, data that is not visible as part of the the form itself but is instead used in the processing of the form, such as identifying the referrer of the form. See ASP.NET databinding section of this document for example of how this is commonly used. |
Close Form Block |
</form> |
</form> . Denotes end of form. |
CGI: The Common Gateway Interface
Common Gateway Interface is a specification for creating executable programs (CGI scripts) that can be run by a web server to carry out dynamic tasks such as:
- Security (e.g. User authorization / IP identification / Software identification)
- Producing dynamic HTML (e.g. Web site visit counters, Advertising (using the metadata refresh tag), reporting the date/time, etc.)
- Querying and updating a database (e.g. Internet Search Engines, Reports).
CGI scripts are very easy to create using languages that can support standard output to a console, such as C/C++, PASCAL, Visual Basic and Perl (Practical Extraction and Report Language). The web server handles the programs by passing output from the programs directly to the calling web browser rather than updating the users console (screen / client window).
The web server generally passes data to the CGI program (script) via environment variables. The CGI program can then look at environment variables using the same method that is used to get the value of an operating system environment variable, (e.g. MS-DOS %PATH%).
When environment variables are passed to CGI scripts they are usually sent using one of two methods, GET and POST. The difference between them is essentially that data passed using the GET method, (the default method) is read by the script as command variables (e.g. read via argc
and argv[]
), and POST data is processed by reading from the standard input. (e.g. read via stdin >>
).
Before we plung into CGI Scripting we need to know a little about the most useful of environment variables, the Query String and also the use of MIME (Multi-purpose Internet Main Extensions)...
CGI: Query Strings
The query string is information that can be passed to a CGI script by a web browser via the URL. If you have used an Internet search engine, such as Yahoo, you may have noticed odd characters popping up (such as, ?, &, %, +) appearing in site address line of the browser, for example:
If you start Yahoo (http://www.yahoo.co.uk/) and search for 'Writing CGI Scripts using C++ ', the following URL is passed by the browser:
http://search.yahoo.co.uk/search/ukie?p=Writing+CGI+Scripts+using+C%2B%2B&y=y.
What this URL does is action (call/execute) the program ukie located at http://search.yahoo.co.uk/search and passed the data p=Writing+CGI+Scripts+using+C%2B%2B&y=y as a query string to the program. (N.B. The first question mark (?) is not included as part of the query string.)
This extra piece of data can be broken down into two parameters where they are split by the ampersand symbol '&', these are:
p=Writing+CGI+Scripts+using+C%2B%2B
This first parameter 'p' is equal to the original search specification entered 'Writing CGI Scripts using C++'. However, because white space characters are not supported as part of a URL, the HTML form converts the spaces to + symbols; and because + symbols mean spaces, the two + symbols (in C++) are converted to HTML format %2B (the hexadecimal ASCII equivalent for the + symbol). (N.B. The data passed to a CGI script from a URL rather than an HTML form is passed as %20 (the ASCII space character) rather than the + symbol, why?)
y=y
This second parameter 'y' is equal to the search space option flag set by the use radio button control to select 'All Sites' (y=y) or 'UK and Ireland Sites Only' (y=u)
CGI: HTTP MIME Headers (Multi-purpose Internet Mail Extensions)
For software, such as a web browsers and e-mail, which are capable of handling several types of information such as plain-text, HTML formatted text and graphic images, we are required to include some additional information which indicates how we desire the content to be processed. Up till this point we have not been required to include this information because it is assumed that information which is passed to a web browser with a file extension of '.HTM' or '.HTML' should be naturally be processed as an HTML document, however CGI programs are passed as raw data by the server rather than documents, so for the web browser to know how to porcess the data from the CGI script we are required to pass an additional piece of information known as a MIME header.
In the our examples we will be passing HTML formatted text between our web server and browser (client). This requires the following plain-text MIME header:
Content-Type: text/html <carriage return>
<carriage return>
Without this information a web browser will interpret any output from our CGI script to be plain-text and will either ignore the data or proceed to treat formatting tags as regular text. MIME headers must be the first line in any information being passed to a browser and they must be on their own line and followed by a blank line. (hence the two carriage returns). For more information regarding MIME headers you should refer to the HTTP/1.1 specification.
CGI Example
CGI 'scripts' can be written using various programming languages (see PERL example later), however for the sake of compatibility with the code in this document, we will be using C++ for our scripting.
CGI C++ programs are very simple to create, technicalities such as multiple user file sharing and communications are handled entirely by the web server, so they are similar to conventional C++ console programs (i.e. simple DOS or UNIX programs).
The following snippet of C++ code simply passes the environment variable QUERY_STRING passed by the web server back to the web browser (client) that called it...
#include <stdlib.h>
#include <iostream.h>
void main()
{
char *EnvVar = getenv("QUERY_STRING");
if (EnvVar == NULL)
EnvVar = "No+Parameters+Passed";
cout << "Content-Type: text/html\n\n";
cout << "<html>";
cout << "Query String: " << EnvVar;
cout << "</html>";
}
When we run this program directly from DOS (or UNIX) the program outputs the expected plain text document back to the console, however when the same program is run through a web server the plain text code is treated as HTML code and the environment string QUERY_STRING is passed back to the web browser as an HTML document.
IIS/ISAPI
The Internet Server Application Programming Interface can be likened to an advanced form of CGI. CGI works on the principle of executing programs on the server when a client (web browser) requests it.
There is a major flaw in this principle because each call to the CGI program requires its own individual instance of the program and thus its own memory space on the server. Therefore if 50 clients are all accessing (hitting) the server then 50 separate instances of the CGI program are required and each CGI environment variable needs to be passed to each of the memory spaces allocated. This is a heavy burden and inefficient use of server resources.
ISAPI programs however work on the principle of Dynamic Link Libraries (DLL's ) which are shared between instances. The draw back to this however is that the programming becomes more complicated because we a required to implement multi-threading in out application. However for our examples we will be using the Microsoft Foundation Classes which hides the multi-threaded work in two classes; CHttpServer
and CHttpServerContext
.
For our purposes we will be writing CGI type applications officially known as ISAPI Extensions, however there is another side of this technology known as ISAPI Filters which are are used to intercept information as it is being passed through the web server. This allows us to carry out tasks such as usage logging, user identification and security, etc.
NSAPI (Netscape Server Application Programming Interface) is as the name suggests, the programming interface for Netscape web servers. However Netscape is intending to change its server architecture to use ISAPI. (Microsoft's ActiveX technology.)
(ISAPI extension DLL's are executed from web pages exactly in the same way that that we execute CGI scripts, i.e. as a hyperlink or actioned from a form.)
ISAPI Extensions
Among the seven MFC classes related to ISAPI we will be using two specific MFC (Microsoft Foundation) classes which are required to create an ISAPI extension DLL. These are:
CHttpServer
- The base class for all ISAPI extensions. This class essentially handles connection between our DLL and the web server.
CHttpServerContext
- The class object that we pass data to which is used to create our virtual dynamic web page. (Similar to the principle used to pass data from CGI using cout <<
.)
With CGI C++ scripting we were required to manually process the QUERY_STRING environment string to establish which parameters have been passed to the script; with ISAPI C++ programming we can use the far simpler method to do this for us known as parse mapping...
ISAPI Parse Mapping (with MFC)
A Parse Map is an MFC (Microsoft Foundation Class) macro used to bind (map) a function to a parameter specified in the Query String passed to our DLL. The easiest way to describe parse mapping is by demonstration.
A parse map is declared in the class CISAPIExampleExtension
which is derived from CHttpServer
. For example...
class CISAPIExampleExtension : public CHttpServer
{
public:
DECLARE_PARSE_MAP()
void Example1(CHttpServerContext* pCtxt);
void Example2(CHttpServerContext* pCtxt, LPCTSTR Param1,
LPCTSTR Param2, LPCTSTR Param3, LPCTSTR Param4,
LPCTSTR Param5, LPCTSTR Param6, LPCTSTR Param7,
LPCTSTR Param8, LPCTSTR Param9, LPCTSTR Param10);
};
BEGIN_PARSE_MAP(CISAPIExampleExtension, CHttpServer)
ON_PARSE_COMMAND(Example1, CISAPIExampleExtension, ITS_EMPTY)
ON_PARSE_COMMAND(Example2, CISAPIExampleExtension,
ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR
ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR)
ON_PARSE_COMMAND_PARAMS("line1= line2= line3= line4=
line5= line6= line7= line8= line9= line10=")
DEFAULT_PARSE_COMMAND(Example1, CISAPIExampleExtension)
END_PARSE_MAP(CISAPIExampleExtension)
Essentially this parse map will carry out the following operations depending on the Query String passed to the DLL.
ISAPIExample.dll - No query string specified, so the default function Example1 which takes no parameters is executed (defined by DEFAULT_PARSE_COMMAND
).
- ISAPIExample.dll?Example1 - The function Example1 is executed.
- ISAPIExample.dll?Example2 - No action (fail), the function Example2 requires parameters.
- ISAPIExample.dll?Example2?line1=Hello&line2=World - Parameters line1 and line 2 are passed to the function Example2. The function actually takes 10 text parameters line1..line10, however these have been defined as optional by placing the equals (=) sign after each parameter and giving a default value of NULL. If these parameters were not defined as optional, then the function would fail unless all ten parameters were supplied in the query string. (N.B. The use of two question marks in a query string is not supported in some early versions of some web browsers.).
- ISAPIExample.dll?Example2?line13=Fail - No action (fail), the function Example2 does not map a parameter named line13.
Database connections (ODBC, OLE DB and ADO)
Database Management Systems (DBMS), such as MS SQL Server, MySQL and Oracle, were designed to save the software developer the trouble of writing their own code to carry out tasks such as, multiple user file handling, indexing/searching (querying) and data security.
ODBC is a standard, developed by Microsoft, to bridge the gap between a database such as an SQL database or indeed a simple text file (supported by the MS Jet Engine) and an application (such as Crystal Reports) that abides by the ODBC rules. For the software developer this generic standard is particularly useful when it comes to system portability. For example an application can be written to process a spreadsheet and update a database at the same time essentially using the same coding principles. Another example might be where an application was written to manipulate data in an Excel Worksheet, is upsized to an Oracle Database without the need to make any changes to the software application, likewise if the software application was changed, perhaps from an MS Windows to an Apple Macintosh environment, there would be no need of making changes to the database.
The foundations of ODBC are based upon an open standard generated by the SQL Access Group (SAG) which is based on the well know relational database Structured Query Language (SQL) which you should be familiar with.
Before we can get stuck in with ODBC, OLE DB and ADO, we need to know a little about the principles for establishing a connection to a database.
Data Source Names (DSN) and Connection Strings
To register a database with the ODBC Manager we need to create a unique Data Source Name (DSN) entry. DSN entries vary between different ODBC drivers and details for these will be provided with your database server.
After creating a DSN entry we can test the connection by using any ODBC application to connect to the database.
Connections are made by passing a connection string to the ODBC manager which contain information such as the DSN and login details. A connection string to connect to my example MS SQL database is:
ODBC;DSN=InternetPAL;UID=sa;PWD=;
This string is then processed by the ODBC manager and fills in the missing information, and presents the full ODBC connection string ... e.g.
ODBC;DSN=InternetPAL;UID=sa;PWD=;APP=ODBC
Test Program;WSID=KEVS;LANGUAGE=us_english;DATABASE=InternetPAL
ODBC Connection String Parameters: |
ODBC; |
Default ODBC connection string (Actually provided for upward compatibility for future connections) |
DSN=InternetPAL |
Data Source Name |
UID=sa; |
User IDentifier ('sa' is the default SQL server systems administrator ID) |
PWD=; |
Password (no password required) |
APP=ODBC Test Program; |
APPlication Name |
WSID=KEVS; |
WorkStation IDentifier |
LANGUAGE=us_english; |
Language |
DATABASE=InternetPAL |
Database Name |
Using MFC, it is very easy to make an ODBC connection. Essentially, all we need to do is create a database object (CDatabase
) and pass the relevant details to the objects class member Open
...
CDatabase m_database;
m_database.Open("", FALSE, TRUE, "ODBC;", FALSE);
CDatabase::Open Parameters: |
"" |
DSN Name: "" (NULL) because we are using ODBC connection strings |
FALSE |
Exclusive: We want shared access rather than exclusive (exclusive access is not actually supported anyway!) |
TRUE |
Read Only: We are only querying the data so do not need write access to the database |
"ODBC;" |
Connection String: Here we are sending the default string |
FALSE |
Load Cursor Library: We do not require the cursor library (default setting) |
This is the completed example function that generates a connection string from the ODBC dialog boxes and connects to a database...
void CODBCTestDoc::OpenOdbc()
{
m_strConnect = "ODBC;";
BeginWaitCursor();
BOOL bRet;
try
{
bRet = m_database.Open("", FALSE, TRUE, m_strConnect, FALSE);
}
catch (CDBException* pe)
{
AfxMessageBox(pe->m_strError);
EndWaitCursor();
pe->Delete();
return;
}
EndWaitCursor();
if (bRet)
{
m_strConnect = m_database.GetConnect();
CDocument::SetTitle(m_strConnect);
OpenRecordset();
}
}
Database recordsets
Once we have connection to a database we can create a recordset (also traditionally know in the database world as a data rowset).
There are several types of which have various advantages and disadvantages in use...
Dynasets - Allow bi-directional scrolling (MoveNext / MovePrevious). Data content changes can be seen by issuing CRecordset::RefreshRowset
. (Sometimes called keyset-driven)
Snapshots - Similar to the camera principle in that a photograph of the data is taken. Bi-directional scrolling is still permitted, however data cannot be updated until the recordset is physically closed and then re-opened.
Dynamic - Similar to dynaset principle, however changes in record sort order can affect other users. (Not widely supported by DBMS's)
Forward Only - Recordsets can only be scrolled from the beginning to end, and can only be read from. This has significant speed advantages however we need to close and re-open the recordset to start again.
Recordsets (CRecordset
objects) are produced easily in the following way...
CRecordset m_pRecordset;
m_pRecordset = new CRecordset(&m_database);
m_pRecordset->Open(CRecordset::dynaset,
"select ... from [Sites]", CRecordset::readOnly);
and for our stored procedure call...
m_pRecordset->Open(CRecordset::dynaset,
"{CALL ODBCTest}", CRecordset::readOnly);
CRecordset::Open Parameters: |
CRecordset::dynaset |
Recordset type (i.e. ::dynaset / ::snapshot / ::dynamic or ::forwardOnly) |
"select ... from [Sites]"
and
"{CALL ODBCTest}"
|
SQL Statement to produce recordset. N.B. The SQL statement must return rows for Open to complete, i.e. you cannot pass statements such as CREATE TABLE because they do not return a data rowset. |
CRecordset::readOnly |
Recordset Option (No writing) |
This is the completed example function that creates a dynaset from the query string held in m_strQuery
.
void CODBCTestDoc::OpenRecordset()
{
CRecordset m_pRecordset;
m_pRecordset = new CRecordset(&m_database);
try
{
if (m_sp == 0)
m_pRecordset->Open(CRecordset::dynaset,
m_strQuery, CRecordset::readOnly);
else
m_pRecordset->Open(CRecordset::dynaset,
"{CALL " + m_strQuery + "}", CRecordset::readOnly);
}
catch (CDBException* pe)
{
m_bConnected = FALSE;
pe->Delete();
return;
}
if (!m_pRecordset->IsBOF())
{
while(!m_pRecordset->IsEOF())
m_pRecordset->MoveNext();
}
m_nRowCount = m_pRecordset->GetRecordCount() + 1;
GetFieldSpecs();
UpdateAllViews(NULL);
m_bConnected = TRUE;
}
Once we have an operational recordset object, we can then think about doing something with it. The table below contains some of the basic properties of a CRecordset
object that you should become familiar with before studying the appendix example ...
Data Members |
m_pRecordset->m_nFields m_pRecordset->m_nParams |
m_nFields : Number of fields
m_nParams : Number of parameters (For use with stored procedure parameters)
|
Tests |
m_pRecordset->IsOpen()
m_pRecordset->IsBOF() m_pRecordset->IsEOF()
m_pRecordset->IsDeleted()
|
IsOpen : Used to test that a recordset has been opened
IsBOF /EOF : Test to see if we are before the first record (BOF) or after the last record (EOF)
IsDeleted : Used to determine if a recordset has been deleted or changed since its last refresh (e.g. perhaps by another user)
|
Adding/Amending Records |
m_pRecordset->CanUpdate() ...if so...
m_pRecordset->Edit() ...or...
m_pRecordset->AddNew() ...finalised by...
m_pRecordset->Update()
m_pRecordset->Delete()
|
Returns true (non-zero) if it is possible to update the recordset
Edit or Create a new record after the current record. (N.B. Update required to finalise the entry)
Delete current record (N.B. Record cursor is automatically reset to BOF, i.e. top of record set) |
Scrolling |
m_pRecordset->CanScroll() ...if so...
m_pRecordset->MoveFirst() m_pRecordset->MovePrev() m_pRecordset->MoveNext() m_pRecordset->MoveLast()
|
Recordset scrolling (First Record, Previous Record, Next Record, Last Record) |
Refreshing |
m_pRecordset->Requery() |
Re-run the SQL query to update the recordset (N.B. Record cursor is automatically reset to BOF, i.e. top of record set) |
m_pRecordset->Close() |
Close Recordset |
ActiveX and the Component Object Model (COM)
The Component Object Model (COM) is the industry standard communications mechanism for executables and dynamic link libraries (DLLs) within the Microsoft Windows and Digital equipment operating systems. Prior to COM, developers who wanted to get porgrams to communicate had a choice of using various ad hoc standards such as DDE (Dynamic Data Exchange), OLE (Object Linking and Embedding) and VBX's (Visual Basic eXtensions). ActiveX is essentially a standard derived from these ad hoc standards and is used to describe the following COM components:
ActiveX Controls
Essentially a control (e.g. Form Controls) are used as an interface for input and output of data to an application. ActiveX Controls are derived from the principles behind OLE controls (OCX's) and VBX's to promote a standard way of re-using the code to support controls between applications, and specifically for use over the World Wide Web.
For software developers it has the benefit that it is platform in-dependent. Controls can be re-used when developing in various environments such as Borland Delphi or Visual Basic and compiled components can be used on Apple and UNIX platforms. (However this technology requires Microsoft products, such as MS Internet Explorer, which has caused some controversy in the software market). Another useful benefit of using ActiveX controls is that their state can be captured and stored in an ActiveX document. The document could be held on an ActiveX Document Server for use perhaps when we are on-line, or we can work with them off-line using any ActiveX enabled software such as MS Word 97.
N.B. ActiveX Controls can be written using MFC (Microsoft Foundation Classes), however controls that are destined for the web should be written using the ActiveX Template Libraries (ATL) because these libraries have been specifically designed to keep the code size of these controls to the bare minimum.
Active Server Pages (ASP)
These are essentially regular HTML documents which contain sections of embedded scripting code that can be be processed by the ISAPI Extension DLL (asp.DLL). The embedded scripting can be written using various methods (such as JavaScript, JScript, PERL) however the default method (and in my opinion, the easiest) is to use Visual Basic Scripting (VBScript). (VBScript is processed by a scripting engine which is in itself a COM/ActiveX component.)
All tasks carried out by Active Server Pages can be hardcoded using the more efficient ISAPI DLL method, however rather than 're-inventing the wheel' it is worth considering the following additional benefits of using ASP:
Session Management - The World Wide Web is a state-less environment. When a user moves from one web page to another any information which may have been entered by them (perhaps into a form) is lost, i.e. the state of the previous page is not maintained. Anyone (such as myself) who has spent time filling in a form to be told that I have missed out a fax number and is presented with a blank form will know what a pain this can be.
Rapid Development- Active Server Pages are easy to code and have the additional benefit that they are interpreted and thus do not require re-compilation which saves a lot of time in the development stage (however because of this they are not as efficient as ISAPI DLLs). ISAPI DLL's are shared files, therefore any program that links to them, i.e. the web server, is required to be shut down and restarted. This operation can take anything up to about a minute, which is a nuisance in itself, however if the web server is being used for other purposes it would need to be carried out at times when it is not busy. (Which for a large international corporation such as Microsoft, would be very rare). Because Active Server Pages are not compiled we can change them at any time without effecting the server.
The example below (AXExample.asp) shows an Active Server Page which carries out a similar task to CGIExample and ISAPIExample by adding any number passed in the QUERY_STRING environment variable:
<%
Query = Request.QueryString()
NumParameters = 1
Dim Param(50)
Param(NumParameters) = Request.QueryString("num1")
do while Param(NumParameters) <> ""
total = total + Int(Param(NumParameters))
NumParameters = NumParameters + 1
Param(NumParameters) =
Request.QueryString("num" + CStr(NumParameters))
loop
NumParameters = NumParameters - 1
if NumParameters > 0 then average = total / NumParameters
%>
<html>
<body bgcolor=#ffffff>
<p ><font size=5 color=#800080><b><i>
Basic example of an ActiveX Server Page
</i></b></font></p>
<font size=2>
This page has been generated by the web server from the page
<b>AXExample.asp</b>. This script splits out
the QUERY_STRING into it's various parameter components
and lists the server environment variables. This script also
demonstrates <i>state</i> on a web page
by calling itself with the original QUERY_STRING +
the additional parameter submitted with the form on this page.
</font>
<hr>
<font color=#000080 face='Arial'><strong>
Command line used to invoke this page
</strong></font><br><br>
<small>
AXExample.asp?<%=Query%>
</small><hr><font color=#000080 face='Arial'>
<strong>QUERY_STRING Parameters split apart</strong></font>
<form action='AXExample.asp' ID="Form1">
<table ID="Table8">
<tr><th>Parameter</th><th>Value</th><th>SubTotal</th></tr>
<%
for f = 1 to NumParameters
%>
<tr>
<td>
num<%=f%>
</td>
<td>
<input type=text name='num<%=f%>'
value='<%=Param(f)%>' ID="Text1">
</td>
</tr>
<%
next
%>
<tr>
<td><td><input type=text
name='num<%=f%>' ID="Text2"></td>
<td><input type=submit value='<<< Add parameter'
ID="Submit1" NAME="Submit1"></td></tr>
</table>
</form>
<font color=RED><b>Total
= <%=total%></b></font> (Average = <%=average%>)
</body>
</html>
Active Data Objects (ADO)
ADO uses similar principles which we met previously in the ODBC section. However the underlying structure is based on COM (today .NET) rather standard Dynamic Link Libraries used by ODBC.
The final result obtained from using ActiveX Data Objects is similar to the principles illustrated by the ISODBCExample. Although this method is less efficient than the ISAPI method (due to the over heads of the interpreter) it is useful for implementing databases which do not support a large number of users.
The example below (ADOExample.asp) demonstrates how to connect to our example database and produce a report from it.
<%
Set Conn = Server.CreateObject("ADODB.Connection")
Conn.Open "dsn=InternetPAL;uid=sa;pwd=;"
Set RS = CreateObject("ADODB.Recordset")
Set RS = Conn.Execute("ISAPIInterface 'ListArticles', '1'")
pos = Request.QueryString("pos")
if Request.QueryString("button") = "First" then pos = 0
if Request.QueryString("button") = "Previous" then pos = pos - 15
if Request.QueryString("button") = "Next" then pos = pos + 15
if Request.QueryString("button") = "Last" then
pos = 0
do while not RS.EOF
pos = pos + 1
RS.MoveNext
loop
RS.MoveFirst
pos = pos - 7
end if
query = Request.QueryString()
%>
<html>
<body bgcolor=#ffffff>
<p ><font size=5 color=#800080><b><i>
Basic example of an ActiveX Data Objects
</i></b></font></p>
<font size=2>
This page has been generated by the web server from
the page <b>ADOExample.asp</b>. Command line used
to invoke this page: ADOExample.asp?<%=Query%>
</font>
<table ID="Table8">
<tr>
<th>
</th>
<%
for i = 0 to RS.Fields.Count - 1
%>
<th align=left><font color=#800000 face="Arial" size=2><u>
<%=RS(i).Name %>
</u></th>
<%
next
for offset = 0 to pos - 9
if Not RS.EOF then RS.MoveNext
next
if pos > 0 then
%>
</tr>
<tr><td><i>+</i></td></tr>
<tr>
<%
end if
f = 0
for t = 1 to 15
if Not RS.EOF then
f = f + 1
%>
<tr><td></td></tr><tr>
<%
for i = 0 to RS.Fields.Count - 1
%>
<td align=left><font face="Arial" size=1><%=RS(i)%></td>
<%
next
%>
</tr>
<%
RS.MoveNext
end if
next
if not RS.EOF then
%>
<tr><td><i>+</td></tr>
<%
end if
RS.Close
Conn.Close
%>
</table>
<form action="ADOExample.asp" ID="Form1">
<input type="submit" name="button" value="First" ID="Submit1">
<input type="submit" name="button" value="Previous" ID="Submit2">
<input type="hidden" name="pos" value="<%=pos%>"
ID="Hidden1"><b><%=pos%></b>
<input type="submit" name="button" value="Next" ID="Submit3">
<input type="submit" name="button" value="Last" ID="Submit4">
</form>
</body>
</html>
ADO/PERL Example: Using ADO to connect to database from a PERL CGI Script
Although it is possible to extend our C++ example (CGIExampe) to access a database. There is a far simpler scripting languages known as PERL (Practical Extraction and Report Language) which is particularly good at processing data. It was originally developed by Larry Wall and uses some familiar structures that we have met in the various C++ examples. The example script below produces a report from our example database.
# Include the OLE (i.e. ActiveX) libraries
use OLE;
# Write MIME Header for HTML document
print "Content-type: text/html\n\n";
print "<html>\n";
# Create ADO connection object
$Conn = CreateObject OLE "ADODB.Connection";
$Conn->Open("InternetPAL");
# Create ADO recordset object
$RS = $Conn->Execute("ISAPIInterface 'ListEntries', '1'");
# Write field names in table header
print " <tr>\n";
$Count = $RS->Fields->count;
for($i = 0; $i < $Count; ++$i)
{
print " <th>", $RS->Fields($i)->name , "</th>\n";
}
print " </tr>\n";
# Populate table with all records
while(!$RS->EOF)
{
print " <tr>\n";
for ( $i = 0; $i < $Count; $i++ )
{
print " <td>", $RS->Fields($i)->value, "</td>\n";
}
print " </tr>\n";
$RS->MoveNext;
}
# Close ADO Recordset and Connection
$RS->Close;
$Conn->Close;
print " </table>\n";
print "</html>\n";
ASP.NET (and the .NET Framework)
The 'traditional' ASP with ADO model described above has a slight hang up in that the code is interpreted. This method has a major flaw which is to say it is somewhat inefficient when deployed on a large enterprise scale.
Developers have tried to skirt some of the problems with interpreted code, such as caching their results etc., however Microsoft has now revised their ASP model significantly so that the embedded source code is now compiled.
What is rather nice about the way this was achieved is that the original source .aspx pages are compiled into the common IL (Interface Language) exposing the .NET Framework. This makes it very easy for developers to now link and run into any other managed code sources they might have sitting around.
A useful side-effect to this is we are no longer restricted to Visual Basic scripting and Jscript, any managed code source can be used, for example regular C# code can be directly transferred into an .aspx page.
For example:
<html>
<script language="C#" runat="server">
int nNumber = 10 * 16;
</script>
<body>
Example of a C# variable nNumber=<%=nNumber%>
</body>
</html>
A slight downside to this is that deploying each .aspx program is a little slower, that is, the process of IIS recompiling/linking the sources on first hit is not as speedy as the interpreted model, however the long term benefits far out weigh this slight delay.
ASP.NET Example: Connecting to a MySQL database using ASP.NET
Here is an example of a console C# application that connects to MySQL using the freely available MySql ADO .NET Connector.
using System;
using MySql.Data.MySqlClient;
public class Kevs
{
protected MySqlConnection sqlConn;
private bool Open(string strDNS)
{
bool bRetVal = false;
try
{
sqlConn = new MySqlConnection(strDNS);
sqlConn.Open();
bRetVal = true;
}
catch (Exception e)
{
}
return bRetVal;
}
private void Close()
{
sqlConn.Close();
}
private void ExecSQL(string strSQL)
{
try
{
MySqlCommand sqlCmd = new MySqlCommand(strSQL);
sqlCmd.Connection = sqlConn;
sqlCmd.ExecuteNonQuery();
}
catch (Exception e)
{
System.Console.WriteLine(e.Message);
}
}
public static void Main()
{
Kevs obj = new Kevs();
if (obj.Open("Database=InternetPAL;Data " +
"Source=localhost;User Id=sa;Password="))
{
obj.ExecSQL("AddYourSQLCommandHere', 0, 0)");
obj.Close();
}
}
}
To convert this code to instead run as an ASP.NET page over the internet is very simple as is can be more or less copy and pasted.
A more reusable method so that you can use your code in other projects and other .ASPX pages would be to import your managed code library... such as
<%@ Import namespace="Your.Managed.Code" %>
(or directly from a managed compiled library .dll)
<%@ Assembly src="YourManagedCode.dll" %>
ASP.NET Data Binding
There are many new features in ASP.NET beyond the scope of this document, however one worthy of mention is databinding. Databinding allows you to code a single .aspx page as if it had some form of internal saved state. In the example below we create an ordinary ASP listbox control and 'bind' the selected item of this listbox using <%# yourBindingVariable%>
. (In the example the listbox object property picker.SelectedItem.Text
is the bound variable.)
<html>
<script language="C#" runat="server">
void kev_Click(Object sender, EventArgs e)
{
Page.DataBind();
}
</script>
<body>
<form runat="server" ID="Form1">
<asp:DropDownList id="picker" runat="server">
<asp:ListItem>Kev</asp:ListItem>
<asp:ListItem>Rach</asp:ListItem>
<asp:ListItem>Someone else</asp:ListItem>
</asp:DropDownList>
<asp:button Text="Submit" OnClick="kev_Click" runat="server/">
</form>
G'day <%# picker.SelectedItem.Text%>
</body>
</html>
The method employed to do this is quite straight forward, an easy way to see how data binding works is to study the JavaScript that the server generates for the browser client: (the trick of course is supplying a hidden value in the web form.)
<html>
<body>
<form method="post" action="example.aspx" id="ctl00">
<div>
<input type="hidden" name="__VIEWSTATE"
value="/wEPDwUKMTI5MzYyOTg5Nw9kFgQCAg9kFgICAQ8PDxYCHgtfIURhdGFCb3Vu
ZGdkZGQCAw8VAQNLZXZkZHUwCgrQcgJvNGiS9QZW7G3q1Ebm" ID="Hidden1"/>
</div>
<select name="picker" id="picker">
<option selected="selected" value="Kev">Kev</option>
<option value="Rach">Rach</option>
<option value="Someone else">Someone else</option>
</select>
<input type="submit" name="ctl01" value="Submit" ID="Submit1"/>
</form>
G'day bound_variable_gets_put_in_here
</body>
</html>
----
Appendix A: ISAPI example code
This is the implementation of the functions Example1 and Example2 (for ISAPIExample.h)
#include <afx.h>
#include <afxisapi.h>
#include "ISAPIExample.h"
CISAPIExampleExtension theExtension;
void CISAPIExampleExtension::Example1(CHttpServerContext* pCtxt)
{
StartContent(pCtxt);
WriteTitle(pCtxt);
*pCtxt <<
" <body bgcolor=#ffffff>\n";
*pCtxt <<
" <p ><font size=5 color=#800080>
<b><i>Basic example of ISAPI (Internet
Server Application Programming Interface)</i>
</b></font></p>\n";
*pCtxt <<
" <font size=2>This page has been generated by the
C++ dynamic link library <b>ISAPIExample.dll</b>
or <b>ISAPIExample.dll?Example1</b>.
</font><br><hr>\n";
*pCtxt <<
" <form action=/examples/ISAPIExample/ISAPIExample.dll?
Example2 method=POST ID="Form1">\n";
*pCtxt << " <table ID="Table3">\n";
*pCtxt <<
" <tr><td><input type=text name=line1
ID="Text1"></td></tr>\n";
*pCtxt <<
" <tr><td><input type=text name=line2
ID="Text2"></td></tr>\n";
*pCtxt <<
" <tr><td><input type=text name=line3
ID="Text3"></td></tr>\n";
*pCtxt <<
" <tr><td><input type=submit
value='Submit to ISAPIExample.dll?Example2'
ID="Submit1" NAME="Submit1">\n";
*pCtxt << " </table>\n";
*pCtxt << " </form>\n";
*pCtxt << " </body>\n";
EndContent(pCtxt);
}
void CISAPIExampleExtension::Example2(CHttpServerContext* pCtxt,
LPCTSTR Param1, LPCTSTR Param2, LPCTSTR Param3, LPCTSTR Param4,
LPCTSTR Param5, LPCTSTR Param6, LPCTSTR Param7, LPCTSTR Param8,
LPCTSTR Param9, LPCTSTR Param10)
{
StartContent(pCtxt);
WriteTitle(pCtxt);
CString Param[11] = {Param1, Param2, Param3, Param4, Param5,
Param6, Param7, Param8, Param9, Param10}, Params = "";
char NumString[20], ParamConversion[20],
TotConversion[20], *AvgConversion = "";
int f = 0;
while (Param[f] > " " && f < 10)
{
itoa(f + 1, NumString, 10);
Params = Params + "line" + NumString + "=" + Param[f] + "&";
f++;
}
*pCtxt << " <body bgcolor=#ffffff>\n";
*pCtxt <<
" <p ><font size=5 color=#800080>
<b><i>Example 2: ISAPI function that takes
several parameters</i></b></font></p>\n";
*pCtxt <<
" <font size=2>This page has been generated by the C++
dynamic link library <b>ISAPIExample.dll?Example2?"
<< Params
<< "</b>.</font><br><hr>\n";
*pCtxt <<
" <form action=/examples/ISAPIExample/ISAPIExample.dll?
Example2 method=POST ID="Form2">\n";
*pCtxt << " <table ID="Table4">\n";
*pCtxt <<
" <tr><th>Parameter</th>
<th>Value</th><th>SubTotal
</th></tr>\n";
f = 0;
int total = 0;
while (Param[f] > " " && f < 10)
{
itoa(f + 1, NumString, 10);
itoa(atoi(Param[f]), ParamConversion, 10);
total = total + atoi(Param[f]);
itoa(total, TotConversion, 10);
*pCtxt << " <tr><td>line"
<< NumString << "</td><td><input
type=text name='line" << NumString << "'
value='" << Param[f]
<< "' ID="Text4"></td><td><i>"
<< TotConversion
<< "</i></td><td><small>(Extracted "
<< ParamConversion
<< " from parameter)</small></td></tr>";
f++;
}
if (f < 10)
{
itoa(f + 1, NumString, 10);
*pCtxt <<
" <tr><td></td><td><input
type=text name='line" << NumString
<< "' ID="Text5"></td><td><input
type=submit value='<<< Add parameter' ID="Submit2"
NAME="Submit2"></td></tr>\n";
}
*pCtxt << " </table>\n";
*pCtxt << " </form>\n";
itoa(total, TotConversion, 10);
itoa(total / f, AvgConversion, 10);
*pCtxt << " <font color=RED><b>Total = "
<< TotConversion << "</b></font>
(Average = " << AvgConversion << ")\n";
*pCtxt << " </body>\n";
EndContent(pCtxt);
}
Appendix B: Connecting to a database from ISAPI using ODBC example code
This page describes the steps involved in implementing the example ISAPI extension DLL.
Now that we are familiar with the basics concepts of ODBC and ISAPI we can put the two together to create a program which produced dynamic HTML code from an SQL database.
One major issue which was missing from this example was how we update the database rather than just querying it. This can be achieved using RFX macros:
ISAPI/ODBC Example: Record Field Exchange (RFX)
RFX macros (see also parse maps) are used to bind (map) database rowset field data (and stored procedure parameters) to data members in our object derived from CRecordset.
To demonstrate RFX I will use an example class CISODBCExample derived from CRecordset.
The class contains three data members which are arrays of CString objects which have been given the following the following roles:
m_Field [6]
- Used to hold 6 data fields from the current database record into the 6 subscripts (0..5) of our array.
m_StoredProcParam
, and m_Pass [6]
- Used to pass 6 parameters (0..5) to the stored procedure defined by m_StoredProcParamas.
class CISODBCData : public CRecordset
{
public:
CString m_Field[6];
CString m_StoredProcParam;
CString m_Pass[6];
DECLARE_DYNAMIC(CISODBCData)
CISODBCData(CDatabase* pDatabase = NULL);
virtual CString GetDefaultConnect();
virtual CString GetDefaultSQL();
virtual void DoFieldExchange(CFieldExchange* pFX);
};
IMPLEMENT_DYNAMIC(CISODBCData, CRecordset)
ISAPI/ODBC Example: Initializing the CISODBCExample
object.
The two CRecordset
data members m_nFields
and m_nParams
must be overridden with the number of fields (6) and the number of parameters (6) respectively. This is done in the constructor that initializes the public CRecordset
database data member 'pdb'. We also initialize our defined data members with null strings.
CISODBCData::CISODBCData(CDatabase* pdb) : CRecordset(pdb)
{
m_Field[0] = _T("");
m_Field[1] = _T("");
m_Field[2] = _T("");
m_Field[3] = _T("");
m_Field[4] = _T("");
m_Field[5] = _T("");
m_nFields = 6;
m_nDefaultType = snapshot;
m_StoredProcParam = _T("");
m_Pass[0] = _T("");
m_Pass[1] = _T("");
m_Pass[2] = _T("");
m_Pass[3] = _T("");
m_Pass[4] = _T("");
m_Pass[5] = _T("");
m_nParams = 6 + 1;
}
CString CISODBCData::GetDefaultConnect()
{
return _T("ODBC;DSN=InternetPAL;UID=sa;PWD=");
}
CString CISODBCData::GetDefaultSQL()
{
return _T("{CALL ISAPIInterface (?, ?, ?, ?, ?, ?, ?)}");
}
ISAPI/ODBC Example: Binding the database data to our data members using RFX Macros
To bind these data members to database fields we use RFX macros in the DoFieldExchange
function. For example:
RFX_Text(pFX, _T("Field1"), m_Field[1]);
The macro RFX_TEXT
binds the database rowset field named Field 1 to our data member m_Field[1]. What this macro does for us is to help update the data depending on which data member has changed. When we move to another record in the recordset, perhaps using RS.MoveNext
, the CString
in m_Field[1] is updated with the text data from the database field "Field1" when we call the DoFieldExchange
function. Likewise when we change the contents of the data member m_Field[1] it updates the database.
If the data we are exchanging is not text, then we cannot use the RFX_TEXT
macro, we must use one of the other RFX macros depending on the data type required:
Macro to use depending on the database field type |
Exchanges data with a C++ data member of type |
RFX_Int |
int |
RFX_Long |
long |
RFX_Bool |
bool (true / false ) |
RFX_Single |
float |
RFX_Double |
double |
RFX_Binary |
CByteArray |
RFX_LongBinary |
CLongBinary (Used for binary large objects (BLOB's) such as bitmaps) |
RFX_Date |
CTime |
void CISODBCData::DoFieldExchange(CFieldExchange* pFX)
{
pFX->SetFieldType(CFieldExchange::param);
RFX_Text(pFX, _T("Report"), m_StoredProcParam);
RFX_Text(pFX, _T("Pass1"), m_Pass[1]);
RFX_Text(pFX, _T("Pass2"), m_Pass[2]);
RFX_Text(pFX, _T("Pass3"), m_Pass[3]);
RFX_Text(pFX, _T("Pass4"), m_Pass[4]);
RFX_Text(pFX, _T("Pass5"), m_Pass[5]);
RFX_Text(pFX, _T("Pass6"), m_Pass[6]);
pFX->SetFieldType(CFieldExchange::outputColumn);
RFX_Text(pFX, _T("Field1"), m_Field[1]);
RFX_Text(pFX, _T("Field2"), m_Field[2]);
RFX_Text(pFX, _T("Field3"), m_Field[3]);
RFX_Text(pFX, _T("Field4"), m_Field[4]);
RFX_Text(pFX, _T("Field5"), m_Field[5]);
RFX_Text(pFX, _T("Field6"), m_Field[6]);
};
ISAPI/ODBC Example: Connecting to an example ISAPI Extension DLL
Once we have established our CRecordset
class we can included it directly an ISAPI class derived from CHttpServer. This example displays the rowset generated by the stored procedure ISAPIInterface in the example database 'InternetPAL'. Using the same stored procedure we can add/delete records and update the database by passing a form containing the 6 fields to the procedure.
The following ISAPI extension code has eleven functions of which the four are directly accessible via the parse map:
- Default - About box. (ISODBCExample.dll)
- Open Recordset - Connects to the InternetPAL database, creates a recordset from the stored procedure ISAPIInterface and executes the show table function. (ISODBCExample.dll?OpenRecordset)
- Close Recordset - Closes recordset and the connection to the database. (ISODBCExample.dll?CloseRecordset)
- Show - This function behave differently depending on the value of the parameter 'Option' passed to it:
- First - Show the first record. (ISODBCExample.dll?Show?Option=First)
- Prev - Show the previous record. (ISODBCExample.dll?Show?Option=Prev)
- Next - Show the next record. (ISODBCExample.dll?Show?Option=Next)
- Last - Show the last record. (ISODBCExample.dll?Show?Option=Last)
- Current - Show the current record (default option). (ISODBCExample.dll?Show)
- Table - Show the recordset in a table view. (ISODBCExample.dll?Show?Option=Table)
- Form - Show the current record in a form view. (ISODBCExample.dll?Show?Option=Form)
- Edit - Show the current record in an edit form. (ISODBCExample.dll?Show?Option=Edit)
- New - Show a blank record for adding to the database. (ISODBCExample.dll?Show?Option=New).
- Delete - Delete the current record (and show first record). (ISODBCExample.dll?Show?Option=Delete)
- Locate - Show the record where its OID (Object ID) = the parameter option. (ISODBCExample.dll?Show?Option=1 ... ,2,5,20)
#include "ISODBCData.h" // Include database class
class CISODBCExample : public CHttpServer
{
public:
CISODBCExample();
~CISODBCExample();
CDatabase db;
TR Field1, rs;
CString Previous_repNo;
int DBView;
char DataState;
void Default(CHttpServerContext* pCtxt);
void OpenRecordset(CHttpServerContext* pCtxt,
LPCTSTR repNo, LPCTSTR param);
void CloseRecordset(CHttpServerContext* pCtxt);
void ShowTable(CHttpServerContext* pCtxt);
void WriteTitle(CHttpServerContext* pCtxt);
void Show(CHttpServerContext* pCtxt, LPCTSTR option);
void PutRecord(CHttpServerContext* pCtxt);
void EditRecord(CHttpServerContext* pCtxt);
void NewRecord(CHttpServerContext* pCtxt);
void UpdateDB(CHttpServerContext* pCtxt, LPCTSTR Field1,
LPCTSTR Field2, LPCTSTR Field3, LPCTSTR Field4,
LPCTSTR Field5, LPCTSTR Field6);
void AddToDB(CHttpServerContext* pCtxt, LPCTSTR Field1,
LPCTSTR Field2, LPCTSTR Field3, LPCTSTR Field4,
LPCTSTR Field5, LPCTSTR Field6);
void DeleteFromDB(CHttpServerContext* pCtxt);
DECLARE_PARSE_MAP()
};
#include <afx.h> // Include the standard MFC classes
#include <afxisapi.h> // Include the ISAPI MFC classes
#include <afxdb.h> // Include the MFC Database classes
#include "ISODBCExample.h" // Include ISAPI extension header
CISODBCExample theExtension;
BEGIN_PARSE_MAP(CISODBCExample, CHttpServer)
ON_PARSE_COMMAND(OpenRecordset, CISODBCExample, ITS_PSTR ITS_PSTR)
ON_PARSE_COMMAND_PARAMS("Recordset=ListEntries Pass1=1")
ON_PARSE_COMMAND(Show, CISODBCExample, ITS_PSTR)
ON_PARSE_COMMAND_PARAMS("Option=Current")
ON_PARSE_COMMAND(CloseRecordset, CISODBCExample, ITS_EMPTY)
ON_PARSE_COMMAND(UpdateDB, CISODBCExample,
ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR)
ON_PARSE_COMMAND_PARAMS("Pass1= Pass2= Pass3= Pass4= Pass5= Pass6=")
ON_PARSE_COMMAND(AddToDB, CISODBCExample,
ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR)
ON_PARSE_COMMAND_PARAMS("Pass1= Pass2= Pass3= Pass4= Pass5= Pass6=")
ON_PARSE_COMMAND(Default, CISODBCExample, ITS_EMPTY)
DEFAULT_PARSE_COMMAND(Default, CISODBCExample)
END_PARSE_MAP(CISODBCExample)
CISODBCExample::CISODBCExample()
{
DBView = 1;
}
CISODBCExample::~CISODBCExample()
{
if (DataState == 1)
{
rs.Close();
db.Close();
}
}
void CISODBCExample::Default(CHttpServerContext* pCtxt)
{
StartContent(pCtxt);
*pCtxt << _T("<body bgcolor=#ffffff>\n");
*pCtxt << _T(" <p ><font size=5 color=#800080>
<b><i>Example of ISAPI and
ODBC</i></b></font></p>\n");
*pCtxt << _T(" ISODBCExample: ISAPI Server Extension
which uses ODBC32, Copyright Kevin Staunton-Lambert
1997<br><br>\n");
*pCtxt << _T("</body>\n");
EndContent(pCtxt);
}
void CISODBCExample::OpenRecordset(CHttpServerContext* pCtxt,
LPCTSTR repNo, LPCTSTR param)
{
StartContent(pCtxt);
if (!DataState)
{
try
{
db.OpenEx(_T("DSN=InternetPAL;UID=sa;PWD="),
CDatabase::noOdbcDialog);
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
rs.m_StoredProcParam = (CString)repNo;
rs.m_Pass[1] = param;
try
{
if (rs.Open(AFX_DB_USE_DEFAULT_TYPE,
_T("{CALL ISAPIInterface (?, ?, ?, ?, ?, ?, ?)}") ,NULL))
{
DataState = 1;
Show(pCtxt, "Table");
}
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
else
{
CloseRecordset(pCtxt);
OpenRecordset(pCtxt, repNo, "1");
}
*pCtxt << _T("</body>\n");
EndContent(pCtxt);
Previous_repNo = repNo;
}
void CISODBCExample::CloseRecordset(CHttpServerContext* pCtxt)
{
StartContent(pCtxt);
if (DataState == 1)
{
DataState = 0;
try
{
rs.Close();
db.Close();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
else
*pCtxt << "Error: Recordset already closed\n";
*pCtxt << _T("</body>\n");
EndContent(pCtxt);
}
void CISODBCExample::WriteTitle(CHttpServerContext* pCtxt)
{
*pCtxt << _T("<head><title>ISODBC Example
</title></head>\n");
}
void CISODBCExample::ShowTable(CHttpServerContext* pCtxt)
{
CODBCFieldInfo Field;
int i = 1, f = 1;
CString TempField, TempOID;
TempOID = rs.m_Field[1];
*pCtxt << _T("<table border=\"0\" ID="Table8"><thead>
<tr><th></th><b>\n");
for (f = 2; f <= (int)rs.m_nFields; f++)
{
try
{
rs.GetODBCFieldInfo(f - 1, Field);
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
*pCtxt
<< _T("<TH align=LEFT><font color=#800000
face=\"Arial\" size=2><U>")
<< _T(Field.m_strName) << _T("</U></th>\n");
}
for (f = 1; f < 10 && !rs.IsBOF(); f++)
{
try
{
rs.MovePrev();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
if (!rs.IsBOF())
*pCtxt
<< _T("<tr><td><i>+</td></tr>\n");
else
{
try
{
rs.MoveNext();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
while (i < 25 && !rs.IsEOF())
{
*pCtxt
<< _T("</tr></b></thead><font face=Arial size=1>\n");
if (TempOID == rs.m_Field[1])
*pCtxt
<< _T("<TR BGcolor=#00FFFF><td><b>></td>\n");
else
*pCtxt << _T("<tr><td></td>\n");
for (f = 2; f <= (int)rs.m_nFields; f++)
*pCtxt
<< _T("<td align=left><font face=\"Arial\" size=1>")
<< _T(rs.m_Field[f]) << _T("</td>\n");
*pCtxt << _T("</tr>\n");
try
{
rs.MoveNext();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
i++;
}
if (!rs.IsEOF()) *pCtxt
<< _T("<tr><td><i>+</td></tr>\n");
*pCtxt << _T("</font></table>\n");
try
{
rs.MoveFirst();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
while (!rs.IsEOF() && rs.m_Field[1] != TempOID)
{
try
{
rs.MoveNext();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
}
void CISODBCExample::PutRecord(CHttpServerContext* pCtxt)
{
CODBCFieldInfo Field;
*pCtxt << _T("<body bgcolor=#ffffff>\n");
*pCtxt << _T("<table ID="Table9">\n");
for (int f = 2; f <= (int)rs.m_nFields; f++)
{
try
{
rs.GetODBCFieldInfo(f - 1, Field);
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
if (Field.m_strName > "")
{
*pCtxt << _T("<tr><td align=right>
<font color=#800000 face=\"Arial\" size=2>")
<< _T(Field.m_strName) << _T("\n");
*pCtxt << _T(": </td><td align=left>
<font color=#000000 face=\"Arial\" size=2>")
<< _T(rs.m_Field[f]) << _T("</td></tr>\n");
}
}
*pCtxt << _T("</font></table>\n");
}
void CISODBCExample::EditRecord(CHttpServerContext* pCtxt)
{
CODBCFieldInfo Field;
char* TempStr = "";
*pCtxt << _T("<form action='/examples/ISODBCExample/
ISODBCExample.dll?UpdateDB'
method=post ID="Form1">\n");
*pCtxt << _T("<input name='Pass1' type=hidden value='")
<< _T(rs.m_Field[1])
<< "' ID="Hidden1"><table ID="Table10">\n";
for (int f = 2; f <= (int)rs.m_nFields; f++)
{
try
{
rs.GetODBCFieldInfo(f - 1, Field);
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
if (Field.m_strName > "")
{
*pCtxt << _T("<tr><td align=right>
<font color=#800000 face=\"Arial\" size=2>")
<< _T(Field.m_strName) << _T(":</td>\n");
*pCtxt << _T("<td><input name='Pass");
itoa(f, TempStr, 10);
*pCtxt << _T((CString)*TempStr);
*pCtxt << _T("' type=text value='");
*pCtxt << _T(rs.m_Field[f])
<< _T("'></td></tr>\n");
}
}
*pCtxt << _T("</font><tr><td>
<input type=submit value='Update Record'>
</td></tr></table></form>\n");
}
void CISODBCExample::NewRecord(CHttpServerContext* pCtxt)
{
CODBCFieldInfo Field;
char* TempStr = "";
*pCtxt << _T("<body bgcolor=#ffffff>\n");
*pCtxt << _T("<form action='/examples/ISODBCExample/
ISODBCExample.dll?AddToDB' method=post>\n");
*pCtxt
<< _T("<input name='Pass1' type=hidden value='")
<< _T(rs.m_Field[1]) << _T("'><table>\n");
for (int f = 2; f <= (int)rs.m_nFields; f++)
{
try
{
rs.GetODBCFieldInfo(f - 1, Field);
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
if (Field.m_strName > "")
{
*pCtxt
<< _T("<tr><td align=right>
<font color=#800000 face=\"Arial\" size=2>")
<< _T(Field.m_strName) << _T(": ");
*pCtxt << _T("<input name='Pass");
itoa(f, TempStr, 10);
*pCtxt << _T((CString)*TempStr);
*pCtxt << _T("' type=text></td></tr>\n");
}
}
*pCtxt
<< _T("</font><tr><td><input
type=submit value='Update Record'></td></tr>
</table></form>");
}
void CISODBCExample::Show(CHttpServerContext* pCtxt, LPCTSTR option)
{
StartContent(pCtxt);
*pCtxt << _T("<body bgcolor=#ffffff>\n");
*pCtxt
<< _T(" <p ><font size=5 color=#800080>
<b><i>Example of ISAPI and ODBC</i>
</b></font></p>\n");
*pCtxt
<< _T(" <font size=2>This page has been generated
by the ISAPI extension DLL <b>ISODBCExample.dll</b>.
This program allows viewing and updation of articles found
in the InternetPAL database.</font><br><hr>\n");
CString opt = option, TempOID = rs.m_Field[1];
if (DataState)
{
if (opt == "First")
{
try
{
rs.MoveFirst();
}
catch(CDBException* e)
{
*pCtxt << e- ID="Text1">m_strError;
return;
}
switch (DBView)
{
case 2: PutRecord(pCtxt); break;
default: ShowTable(pCtxt); break;
}
}
else if (opt == "Prev")
{
if (DBView == 1)
{
while (!rs.IsBOF() && rs.m_Field[1] == TempOID)
{
try
{
rs.MovePrev();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
}
ShowTable(pCtxt);
}
else
{
try
{
rs.MovePrev();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
PutRecord(pCtxt);
}
}
else if (opt == "Next")
{
if (DBView == 1)
{
while (!rs.IsEOF() && rs.m_Field[1] == TempOID)
{
try
{
rs.MoveNext();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
ShowTable(pCtxt);
}
else
{
try
{
rs.MoveNext();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
PutRecord(pCtxt);
}
}
else if (opt == "Last")
{
try
{
rs.MoveLast();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
switch (DBView)
{
case 2: PutRecord(pCtxt); break;
default: ShowTable(pCtxt); break;
}
}
else if (opt == "Current")
{
switch (DBView)
{
case 2: PutRecord(pCtxt); break;
default: ShowTable(pCtxt); break;
}
}
else if (opt == "Table")
{
DBView = 1;
ShowTable(pCtxt);
}
else if (opt == "Form")
{
DBView = 2;
PutRecord(pCtxt);
}
else if (opt.Left(4) == "Edit")
{
EditRecord(pCtxt);
}
else if (opt.Left(3) == "New")
{
NewRecord(pCtxt);
}
else if (opt.Left(6) == "Delete")
{
DeleteFromDB(pCtxt);
}
else
{
try
{
rs.MoveFirst();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
int Found = 0;
while (!rs.IsEOF() && !Found)
{
if (rs.m_Field[1] == opt)
{
switch (DBView)
{
case 2: PutRecord(pCtxt); break;
default: ShowTable(pCtxt); break;
}
Found = 1;
}
else
{
try
{
rs.MoveNext();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
}
if (!Found) *pCtxt
<< "Could not match a record with Object ID: "
<< opt << _T("\n");
}
if (rs.IsBOF()) *pCtxt << "<i>(First Record)</i>\n";
if (rs.IsEOF()) *pCtxt << "<i>(Last Record)</i>\n";
}
else
*pCtxt << "Error: Recordset not open\n";
*pCtxt << _T("</body>\n");
EndContent(pCtxt);
}
void CISODBCExample::UpdateDB(CHttpServerContext* pCtxt,
LPCTSTR Field1, LPCTSTR Field2, LPCTSTR Field3,
LPCTSTR Field4, LPCTSTR Field5, LPCTSTR Field6)
{
StartContent(pCtxt);
if (rs.CanUpdate())
{
try
{
rs.Edit();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
if (Field1 > "") rs.m_Field[1] = Field1;
if (Field2 > "") rs.m_Field[2] = Field2;
if (Field3 > "") rs.m_Field[3] = Field3;
if (Field4 > "") rs.m_Field[4] = Field4;
if (Field5 > "") rs.m_Field[5] = Field5;
if (Field6 > "") rs.m_Field[6] = Field6;
try
{
rs.Update();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
else
{
CISODBCData Temp_rs;
Temp_rs.m_StoredProcParam = "Update" + (CString)Previous_repNo;
Temp_rs.m_Pass[1] = Field1;
Temp_rs.m_Pass[2] = Field2;
Temp_rs.m_Pass[3] = Field3;
Temp_rs.m_Pass[4] = Field4;
Temp_rs.m_Pass[5] = Field5;
Temp_rs.m_Pass[6] = Field6;
try
{
Temp_rs.Open(AFX_DB_USE_DEFAULT_TYPE,
_T("{CALL ISAPIInterface (?, ?, ?, ?, ?, ?, ?)}"),
NULL);
Temp_rs.Close();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
rs.Requery();
Show(pCtxt, Field1);
*pCtxt << _T("</body>\n");
EndContent(pCtxt);
}
void CISODBCExample::AddToDB(CHttpServerContext* pCtxt,
LPCTSTR Field1, LPCTSTR Field2, LPCTSTR Field3,
LPCTSTR Field4, LPCTSTR Field5, LPCTSTR Field6)
{
if (rs.CanUpdate())
{
try
{
rs.AddNew();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
if (Field1 > "") rs.m_Field[1] = Field1;
if (Field2 > "") rs.m_Field[2] = Field2;
if (Field3 > "") rs.m_Field[3] = Field3;
if (Field4 > "") rs.m_Field[4] = Field4;
if (Field5 > "") rs.m_Field[5] = Field5;
if (Field6 > "") rs.m_Field[6] = Field6;
try
{
rs.Update();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
else
{
CISODBCData Temp_rs;
Temp_rs.m_StoredProcParam = "Add" + (CString)Previous_repNo;
Temp_rs.m_Pass[1] = Field1;
Temp_rs.m_Pass[2] = Field2;
Temp_rs.m_Pass[3] = Field3;
Temp_rs.m_Pass[4] = Field4;
Temp_rs.m_Pass[5] = Field5;
Temp_rs.m_Pass[6] = Field6;
try
{
Temp_rs.Open(AFX_DB_USE_DEFAULT_TYPE,
_T("{CALL ISAPIInterface (?, ?, ?, ?, ?, ?, ?)}") ,NULL);
Temp_rs.Close();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
try
{
rs.Requery();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
while (Field2 != rs.m_Field[2] &&
Field3 != rs.m_Field[3] && !rs.IsEOF())
{
try
{
rs.MoveNext();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
switch (DBView)
{
case 2: PutRecord(pCtxt); break;
default: ShowTable(pCtxt); break;
}
*pCtxt << _T("</body>\n");
EndContent(pCtxt);
}
void CISODBCExample::DeleteFromDB(CHttpServerContext* pCtxt)
{
StartContent(pCtxt);
if (rs.CanUpdate())
{
try
{
rs.Delete();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
else
{
CISODBCData Temp_rs;
Temp_rs.m_StoredProcParam = "Delete" + (CString)Previous_repNo;
Temp_rs.m_Pass[1] = rs.m_Field[1];
try
{
Temp_rs.Open(AFX_DB_USE_DEFAULT_TYPE,
_T("{CALL ISAPIInterface (?, ?)}") ,NULL);
Temp_rs.Close();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
}
try
{
rs.Requery();
}
catch(CDBException* e)
{
*pCtxt << e->m_strError;
return;
}
switch (DBView)
{
case 2: PutRecord(pCtxt); break;
default: ShowTable(pCtxt); break;
}
*pCtxt << _T("</body>\n");
EndContent(pCtxt);
}
Appendix C: Example MS SQL database used in examples
This is the Transact-SQL required to recreate the example database that gets connected to by the various ODBC/ADO examples in this document.
if exists (select * from sysobjects where
id = object_id('dbo.GetNextClassification')
and sysstat & 0xf = 4)
drop procedure "dbo"."GetNextClassification"
GO
if exists (select * from sysobjects where
id = object_id('dbo.ISAPIInterface') and sysstat & 0xf = 4)
drop procedure "dbo"."ISAPIInterface"
GO
if exists (select * from sysobjects where
id = object_id('dbo.ListAllKeywords') and sysstat & 0xf = 4)
drop procedure "dbo"."ListAllKeywords"
GO
if exists (select * from sysobjects where
id = object_id('dbo.ListArticles') and sysstat & 0xf = 4)
drop procedure "dbo"."ListArticles"
GO
if exists (select * from sysobjects where
id = object_id('dbo.ListIndexes') and sysstat & 0xf = 4)
drop procedure "dbo"."ListIndexes"
GO
if exists (select * from sysobjects where
id = object_id('dbo.ListMatches') and sysstat & 0xf = 4)
drop procedure "dbo"."ListMatches"
GO
if exists (select * from sysobjects where
id = object_id('dbo.ListRelated') and sysstat & 0xf = 4)
drop procedure "dbo"."ListRelated"
GO
if exists (select * from sysobjects where
id = object_id('dbo.ListRelatedKeywords') and sysstat & 0xf = 4)
drop procedure "dbo"."ListRelatedKeywords"
GO
if exists (select * from sysobjects where
id = object_id('dbo.ListURLs') and sysstat & 0xf = 4)
drop procedure "dbo"."ListURLs"
GO
if exists (select * from sysobjects where
id = object_id('dbo.Search') and sysstat & 0xf = 4)
drop procedure "dbo"."Search"
GO
if exists (select * from sysobjects where id = object_id('dbo.SearchSounds')
and sysstat & 0xf = 4)
drop procedure "dbo"."SearchSounds"
GO
if exists (select * from sysobjects where id = object_id('dbo.ClassCode')
and sysstat & 0xf = 4)
drop procedure "dbo"."ClassCode"
GO
if exists (select * from sysobjects where id = object_id('dbo.Entries')
and sysstat & 0xf = 3)
drop table "dbo"."Entries"
GO
if exists (select * from sysobjects where id =
object_id('dbo.Indexes') and sysstat & 0xf = 3)
drop table "dbo"."Indexes"
GO
if exists (select * from sysobjects where
id = object_id('dbo.Thesaurus') and sysstat & 0xf = 3)
drop table "dbo"."Thesaurus"
GO
if exists (select * from sysobjects where id = object_id('dbo.URLs')
and sysstat & 0xf = 3)
drop table "dbo"."URLs"
GO
CREATE TABLE "dbo"."Entries" (
"OID" "int" NOT NULL ,
"Name" varchar (100) NOT NULL ,
"URL" "int" NOT NULL ,
"Bookmark" varchar (255) NULL ,
CONSTRAINT "PK___3__10" PRIMARY KEY CLUSTERED
(
"OID"
),
CONSTRAINT "NoDuplicateNames" UNIQUE NONCLUSTERED
(
"Name"
),
CONSTRAINT "OID" CHECK (OID >= 100000000 and (OID <= 999999999))
)
GO
CREATE TABLE "dbo"."Indexes" (
"OID" "int" IDENTITY (1, 1) NOT NULL ,
"Name" varchar (100) NOT NULL ,
"Base_Entry_OID" "int" NOT NULL ,
"Top_Entry_OID" "int" NOT NULL ,
CONSTRAINT "PK___2__10" PRIMARY KEY CLUSTERED
(
"OID"
),
CONSTRAINT "NoDuplicateIndexNames" UNIQUE NONCLUSTERED
(
"Name"
),
CONSTRAINT "BaseEntry" CHECK (Base_Entry_OID >= 100000000
and (Base_Entry_OID <= 999999999)),
CONSTRAINT "TopEntry" CHECK (Top_Entry_OID >= 100000000
and (Top_Entry_OID <= 999999999))
)
GO
CREATE TABLE "dbo"."Thesaurus" (
"OID" "int" IDENTITY (1, 1) NOT NULL ,
"Entry_OID" "int" NOT NULL ,
"String" varchar (30) NOT NULL ,
CONSTRAINT "PK___1__10" PRIMARY KEY CLUSTERED
(
"OID"
),
CONSTRAINT "EntryOID" CHECK (Entry_OID >= 100000000
and (Entry_OID <= 999999999))
)
GO
CREATE TABLE "dbo"."URLs" (
"OID" "int" IDENTITY (1, 1) NOT NULL ,
"URL" varchar (255) NOT NULL ,
CONSTRAINT "PK___4__10" PRIMARY KEY CLUSTERED
(
"OID"
),
CONSTRAINT "NoDuplicateURLs" UNIQUE NONCLUSTERED
(
"URL"
)
)
GO
CREATE PROCEDURE ClassCode @Code int = 0 as
DECLARE @ClassCode varchar(13)
SELECT @ClassCode = CONVERT(varchar(9), @Code)
SELECT @ClassCode = SUBSTRING(@ClassCode, 1, 1) + ":" +
SUBSTRING(@ClassCode, 2, 2) + ":" +
SUBSTRING(@ClassCode, 4, 2) + ":" +
SUBSTRING(@ClassCode, 6, 2) + ":" +
SUBSTRING(@ClassCode, 8, 2)
SELECT @ClassCode
GO
CREATE PROCEDURE GetNextClassification @index int = 0AS
DECLARE @Base int
DECLARE @Top int
DECLARE @OID int
SELECT @Base = Base_Entry_OID, @Top = Top_Entry_OID
FROM Indexes
WHERE OID = @index
SELECT @OID = (OID + 1) FROM Entries WHERE Entries.OID
>= @Base AND Entries.OID <= @Top ORDER BY OID
SELECT @OID
GO
CREATE PROCEDURE ISAPIInterface @Action varchar(20) = "DataErr",
@Pass1 varchar(40) = "", @Pass2 varchar(40) = "",
@Pass3 varchar(40) = "", @Pass4 varchar(40) = "",
@Pass5 varchar(40) = "", @Pass6 varchar(40) = "",
@Pass7 varchar(40) = "", @Pass8 varchar(40) = "" AS
IF @Action LIKE "%List%"
BEGIN
DECLARE @Base int
DECLARE @Top int
SELECT @Base = Base_Entry_OID, @Top = Top_Entry_OID
FROM Indexes
WHERE OID = CONVERT(int, @Pass1)
SELECT Thesaurus.OID, Entries.OID AS "Classification",
String AS "Term", Name AS "Article", URLs.URL AS "URL",
Bookmark, NULL, NULL
FROM Entries, URLs, Thesaurus
WHERE Entries.OID >= @Base AND Entries.OID <= @Top
AND Entries.URL = URLs.OID
AND Thesaurus.Entry_OID = Entries.OID
ORDER BY Entries.OID
END
IF @Action LIKE "%Update%"
BEGIN
UPDATE Thesaurus
SET
String = @Pass3
WHERE OID = CONVERT(int, @Pass1)
DECLARE @OID int
SELECT @OID = Entry_OID
FROM Thesaurus
WHERE OID = CONVERT(int, @Pass1)
DECLARE @URL int
SELECT @URL = URL
FROM Entries
WHERE OID = @OID
UPDATE URLs
SET
URL = @Pass5
WHERE OID = @URL
UPDATE Entries
SET
OID = CONVERT(int, @Pass2),
Name = @Pass4,
Bookmark = @Pass6
WHERE OID = @OID
END
IF @Action LIKE "%Add%"
BEGIN
INSERT URLs
VALUES (@Pass5)
SELECT @URL = OID
FROM URLs
WHERE URL = @Pass5
INSERT Entries
VALUES (CONVERT(int, @Pass2), @Pass4, @URL, @Pass5)
INSERT Thesaurus
VALUES (CONVERT(int, @Pass2), @Pass3)
END
IF @Action LIKE "%Delete%"
BEGIN
DELETE FROM Thesaurus
WHERE OID = Convert(int, @Pass1)
END
GO
CREATE PROCEDURE ListAllKeywords AS
SELECT String From Thesaurus
GROUP BY String
ORDER BY String
GO
CREATE PROCEDURE ListArticles AS
SELECT OID, Name
FROM Entries ORDER BY Name
GO
CREATE PROCEDURE ListIndexes AS
SELECT Indexes.OID, "Indent" =
case
when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 9, 1) > "0"
then ". . . . . . . . . . " + Indexes.Name
when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 8, 1) > "0"
then ". . . . . . . . . . " + Indexes.Name
when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 7, 1) > "0"
then ". . . . . . . . . " + Indexes.Name
when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 6, 1) > "0"
then ". . . . . . . . . " + Indexes.Name
when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 5, 1) > "0"
then ". . . . . . " + Indexes.Name
when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 4, 1) > "0"
then ". . . . . . " + Indexes.Name
when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 3, 1) > "0"
then ". . . " + Indexes.Name
when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 2, 1) > "0"
then ". . . " + Indexes.Name
when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 1, 1) > "0"
then Indexes.Name
end
FROM Indexes ORDER BY Base_Entry_OID
GO
CREATE PROCEDURE ListMatches @Entry varchar(150) = "",
@Exclude varchar(150) = "", @Restrict int = 1 AS
DECLARE @Base int
DECLARE @Top int
SELECT @Base = Base_Entry_OID, @Top = Top_Entry_OID
FROM Indexes
WHERE OID = @Restrict
if @Exclude = ""
BEGIN
SELECT "Hyperlink"=
case
when MAX(URLs.URL) = "mailto:" then MAX(URLs.URL + Bookmark)
when MAX(URLs.URL) = "ftp:" then MAX(URLs.URL + Bookmark)
when MAX(Bookmark) > "" then MAX(URLs.URL + "#" + Bookmark)
else MAX(URLs.URL)
end,
"Indent" =
case
when substring(CONVERT(char(9), MAX(Entries.OID)), 9, 1) > "0" then
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 8, 2) +
" <font size=2><b>" + MAX(String) +
" </b><i>" + Entries.Name + "</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 8, 1) > "0" then
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 8, 2) +
" <font size=2><b>" + MAX(String) +
" </b><i>" + Entries.Name + "</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 7, 1) > "0" then
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) +
" <font size=3><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 6, 1) > "0" then
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) +
" <font size=3><b>" + MAX(String) +
" </b><i>" + Entries.Name + "</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 5, 1) > "0" then
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) +
" <font size=4><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 4, 1) > "0" then
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) +
" <font size=4><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 3, 1) > "0" then
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) +
" <font size=5><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 2, 1) > "0" then
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) +
" <font size=5>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 1, 1) > "0" then
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) +
" <font size=6><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
end
FROM Thesaurus, Entries, URLs
WHERE (Entries.OID >= @Base AND Entries.OID <= @Top)
AND (@Entry Like "%" + String + "%" OR String Like "%" +
@Entry + "%" OR @Entry Like "%" + Name + "%" OR Name Like "%" +
@Entry + "%") AND Thesaurus.Entry_OID = Entries.OID
AND Entries.URL = URLs.OID
GROUP BY Name
ORDER BY MAX(Entries.OID)
END
else
BEGIN
SELECT "Hyperlink"=
case
when MAX(URLs.URL) = "mailto:" then MAX(URLs.URL + Bookmark)
when MAX(URLs.URL) = "ftp:" then MAX(URLs.URL + Bookmark)
when MAX(Bookmark) > "" then MAX(URLs.URL + "#" + Bookmark)
else MAX(URLs.URL)
end,
"Indent" =
case
when substring(CONVERT(char(9), MAX(Entries.OID)), 9, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 8, 2) +
" <font size=2><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 8, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 8, 2) +
" <font size=2><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 7, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) +
" <font size=3><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 6, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) +
" <font size=3><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 5, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) +
" <font size=4><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 4, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) +
" <font size=4><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 3, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) +
" <font size=5><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 2, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) +
" <font size=5>" + MAX(String) + " </b><i>"
+ Entries.Name + "</i></font>"
when substring(CONVERT(char(9), MAX(Entries.OID)), 1, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) +
" <font size=6><b>" + MAX(String) +
" </b><i>" + Entries.Name +
"</i></font>"
end
FROM Thesaurus, Entries, URLs
WHERE (Entries.OID >= @Base AND Entries.OID <= @Top)
AND (@Entry Like "%" + String + "%" OR String Like "%" +
@Entry + "%" OR @Entry Like "%" + Name + "%" OR Name Like "%" +
@Entry + "%") AND (@Exclude NOT Like "%" + String + "%"
AND String NOT Like "%" + @Exclude + "%" AND @Exclude NOT Like "%"
+ Name + "%" AND Name NOT Like "%" + @Exclude + "%") AND
Thesaurus.Entry_OID = Entries.OID AND Entries.URL = URLs.OID
GROUP BY Name
ORDER BY MAX(Entries.OID)
END
GO
CREATE PROCEDURE ListRelated @IndexOID int = 1 AS
DECLARE @Base int
DECLARE @Top int
SELECT @Base = Base_Entry_OID, @Top = Top_Entry_OID
FROM Indexes
WHERE OID = @IndexOID
SELECT "Hyperlink"=
case
when URLs.URL = "mailto:" then URLs.URL + Bookmark
when URLs.URL = "ftp:" then URLs.URL + Bookmark
when Bookmark > "" then URLs.URL + "#" + Bookmark
else URLs.URL
end
, "Indent" =
case
when substring(CONVERT(char(9), Entries.OID), 9, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 4, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 6, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 8, 2) +
" <font size=2><b>" + Entries.Name +
"</b></font>"
when substring(CONVERT(char(9), Entries.OID), 8, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 4, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 6, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 8, 2) +
" <font size=2><b>" + Entries.Name +
"</b></font>"
when substring(CONVERT(char(9), Entries.OID), 7, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 4, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 6, 2) +
" <font size=3><b>" + Entries.Name +
"</b></font>"
when substring(CONVERT(char(9), Entries.OID), 6, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 4, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 6, 2) +
" <font size=3><b>" + Entries.Name +
"</b></font>"
when substring(CONVERT(char(9), Entries.OID), 5, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 4, 2) +
" <font size=4><b>" + Entries.Name +
"</b></font>"
when substring(CONVERT(char(9), Entries.OID), 4, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 4, 2) +
" <font size=4><b>" + Entries.Name +
"</b></font>"
when substring(CONVERT(char(9), Entries.OID), 3, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) +
" <font size=5><b>" + Entries.Name +
"</b></font>"
when substring(CONVERT(char(9), Entries.OID), 2, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" +
SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) +
" <font size=5>" + Entries.Name +
"</b></font>"
when substring(CONVERT(char(9), Entries.OID), 1, 1) > "0"
then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) +
" <font size=6><b>" + Entries.Name +
"</b></font>"
end
FROM Entries, URLs
WHERE Entries.OID >= @Base AND
Entries.OID <= @Top AND Entries.URL = URLs.OID
ORDER BY Entries.OID
GO
CREATE PROCEDURE ListRelatedKeywords @IndexOID int = 1 AS
DECLARE @Base int
DECLARE @Top int
SELECT @Base = Base_Entry_OID, @Top = Top_Entry_OID
FROM Indexes WHERE OID = @IndexOID
SELECT "Hyperlink"=
case
when URLs.URL = "mailto:" then URLs.URL + Bookmark
when URLs.URL = "ftp:" then URLs.URL + Bookmark
when Bookmark > "" then URLs.URL + "#" + Bookmark
else URLs.URL
end
, Thesaurus.String
FROM Entries, Thesaurus, URLs
WHERE Entries.OID >= @Base AND Entries.OID <= @Top AND
Entries.OID = Entry_OID AND Entries.URL = URLs.OID
ORDER BY Thesaurus.String, Entries.Name
GO
CREATE PROCEDURE ListURLs AS
SELECT OID, URL
FROM URLs ORDER BY URL
GO
CREATE PROCEDURE Search @Entry varchar(150) = "", @Exclude varchar(150) = "" AS
SELECT MAX(Indexes.OID),
"Indent" =
case
when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 9, 1) > "0"
then ". . . . . . . . . . " + MAX(Indexes.Name)
when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 8, 1) > "0"
then ". . . . . . . . . . " + MAX(Indexes.Name)
when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 7, 1) > "0"
then ". . . . . . . . . " + MAX(Indexes.Name)
when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 6, 1) > "0"
then ". . . . . . . . . " + MAX(Indexes.Name)
when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 5, 1) > "0"
then ". . . . . . " + MAX(Indexes.Name)
when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 4, 1) > "0"
then ". . . . . . " + MAX(Indexes.Name)
when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 3, 1) > "0"
then ". . . " + MAX(Indexes.Name)
when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 2, 1) > "0"
then ". . . " + MAX(Indexes.Name)
when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 1, 1) > "0"
then MAX(Indexes.Name)
end
FROM Thesaurus, Indexes
WHERE (@Entry Like "%" + String + "%" OR String Like "%" + @Entry +
"%" OR @Entry Like "%" + Name + "%" OR Name Like "%" + @Entry + "%")
AND (@Exclude NOT Like "%" + String + "%" AND NOT String Like "%" +
@Exclude + "%") AND Indexes.Base_Entry_OID <= Thesaurus.Entry_OID
AND Indexes.Top_Entry_OID >= Thesaurus.Entry_OID
GROUP BY Indexes.OID
ORDER BY MAX(Indexes.Base_Entry_OID)
GO
CREATE PROCEDURE SearchSounds @Entry varchar(150) = "" AS
SELECT String From Thesaurus
WHERE DIFFERENCE(@Entry, String) = 4 OR @Entry Like "%" + String + "%"
OR String Like "%" + @Entry + "%"
GROUP BY String
ORDER BY String
GO